-
-
-
- {!token || !token.length ?
-
0 ?
+ <>
+
Two-factor authentication enabled
+
+ Two-factor authentication has been enabled on your account. Should you loose access to
+ this device you'll need to use on of the codes displayed below in order to access your
+ account.
+
+
+ These codes will not be displayed again. Please take note of them now
+ by storing them in a secure repository such as a password manager.
+
+
+ {recoveryTokens.map(token => {token}
)}
+
+
+
+
+ >
+ :
+
-
+
+ }
)}
diff --git a/resources/scripts/components/server/ServerConsole.tsx b/resources/scripts/components/server/ServerConsole.tsx
index 1834dbe5f..36ffbb4f8 100644
--- a/resources/scripts/components/server/ServerConsole.tsx
+++ b/resources/scripts/components/server/ServerConsole.tsx
@@ -81,6 +81,9 @@ export default () => {
};
}, [ instance, connected ]);
+ const disklimit = server.limits.disk != 0 ? megabytesToHuman(server.limits.disk) : "Unlimited";
+ const memorylimit = server.limits.memory != 0 ? megabytesToHuman(server.limits.memory) : "Unlimited";
+
return (
@@ -112,7 +115,7 @@ export default () => {
className={'mr-1'}
/>
{bytesToHuman(memory)}
-
/ {megabytesToHuman(server.limits.memory)}
+
/ {memorylimit}
{
className={'mr-1'}
/>
{bytesToHuman(disk)}
- / {megabytesToHuman(server.limits.disk)}
+
+ / {disklimit}
{!server.isInstalling ?
diff --git a/resources/scripts/components/server/backups/BackupContainer.tsx b/resources/scripts/components/server/backups/BackupContainer.tsx
index 438c201fb..1dbe3070e 100644
--- a/resources/scripts/components/server/backups/BackupContainer.tsx
+++ b/resources/scripts/components/server/backups/BackupContainer.tsx
@@ -12,7 +12,7 @@ import { ServerContext } from '@/state/server';
import PageContentBlock from '@/components/elements/PageContentBlock';
export default () => {
- const { uuid } = useServer();
+ const { uuid, featureLimits } = useServer();
const { addError, clearFlashes } = useFlash();
const [ loading, setLoading ] = useState(true);
@@ -50,10 +50,22 @@ export default () => {
/>)}
}
+ {featureLimits.backups === 0 &&
+
+ Backups cannot be created for this server.
+
+ }
+ {(featureLimits.backups > 0 && backups.length > 0) &&
+
+ {backups.length} of {featureLimits.backups} backups have been created for this server.
+
+ }
+ {featureLimits.backups > 0 && featureLimits.backups !== backups.length &&
+ }
);
diff --git a/resources/scripts/components/server/databases/DatabasesContainer.tsx b/resources/scripts/components/server/databases/DatabasesContainer.tsx
index 7fb60f351..9213347f0 100644
--- a/resources/scripts/components/server/databases/DatabasesContainer.tsx
+++ b/resources/scripts/components/server/databases/DatabasesContainer.tsx
@@ -59,7 +59,12 @@ export default () => {
}
- {featureLimits.databases > 0 &&
+ {(featureLimits.databases > 0 && databases.length > 0) &&
+
+ {databases.length} of {featureLimits.databases} databases have been allocated to this server.
+
+ }
+ {featureLimits.databases > 0 && featureLimits.databases !== databases.length &&
diff --git a/resources/scripts/components/server/files/FileEditContainer.tsx b/resources/scripts/components/server/files/FileEditContainer.tsx
index f314744ef..f24e7bc60 100644
--- a/resources/scripts/components/server/files/FileEditContainer.tsx
+++ b/resources/scripts/components/server/files/FileEditContainer.tsx
@@ -110,7 +110,7 @@ export default () => {
fetchContent={value => {
fetchFileContent = value;
}}
- onContentSaved={() => null}
+ onContentSaved={() => save()}
/>
diff --git a/resources/scripts/components/server/files/FileManagerBreadcrumbs.tsx b/resources/scripts/components/server/files/FileManagerBreadcrumbs.tsx
index 5e595a2e5..c125dd2c2 100644
--- a/resources/scripts/components/server/files/FileManagerBreadcrumbs.tsx
+++ b/resources/scripts/components/server/files/FileManagerBreadcrumbs.tsx
@@ -25,10 +25,10 @@ export default ({ withinFileEditor, isNewFile }: Props) => {
.filter(directory => !!directory)
.map((directory, index, dirs) => {
if (!withinFileEditor && index === dirs.length - 1) {
- return { name: directory };
+ return { name: decodeURIComponent(directory) };
}
- return { name: directory, path: `/${dirs.slice(0, index + 1).join('/')}` };
+ return { name: decodeURIComponent(directory), path: `/${dirs.slice(0, index + 1).join('/')}` };
});
return (
@@ -57,7 +57,7 @@ export default ({ withinFileEditor, isNewFile }: Props) => {
}
{file &&
- {file}
+ {decodeURIComponent(file)}
}
diff --git a/resources/scripts/components/server/files/NewDirectoryButton.tsx b/resources/scripts/components/server/files/NewDirectoryButton.tsx
index 86141d213..842cddf8a 100644
--- a/resources/scripts/components/server/files/NewDirectoryButton.tsx
+++ b/resources/scripts/components/server/files/NewDirectoryButton.tsx
@@ -70,7 +70,12 @@ export default () => {
/>
This directory will be created as
- /home/container/{join(directory, values.directoryName).replace(/^(\.\.\/|\/)+/, '')}
+ /home/container/
+
+ {decodeURIComponent(
+ join(directory, values.directoryName).replace(/^(\.\.\/|\/)+/, ''),
+ )}
+
-
-
diff --git a/resources/views/admin/servers/view/build.blade.php b/resources/views/admin/servers/view/build.blade.php
index c3925c074..c1f8defca 100644
--- a/resources/views/admin/servers/view/build.blade.php
+++ b/resources/views/admin/servers/view/build.blade.php
@@ -66,7 +66,7 @@
MB
-
This server will not be allowed to boot if it is using more than this amount of space. If a server goes over this limit while running it will be safely stopped and locked until enough space is available.
+
This server will not be allowed to boot if it is using more than this amount of space. If a server goes over this limit while running it will be safely stopped and locked until enough space is available. Set to 0
to allow unlimited disk usage.
diff --git a/resources/views/admin/servers/view/index.blade.php b/resources/views/admin/servers/view/index.blade.php
index 637c9bd33..b8c17fb01 100644
--- a/resources/views/admin/servers/view/index.blade.php
+++ b/resources/views/admin/servers/view/index.blade.php
@@ -97,7 +97,13 @@
Disk Space |
- {{ $server->disk }}MB |
+
+ @if($server->disk === 0)
+ Unlimited
+ @else
+ {{ $server->disk }}MB
+ @endif
+ |
Block IO Weight |
diff --git a/tests/Integration/Api/Client/AccountControllerTest.php b/tests/Integration/Api/Client/AccountControllerTest.php
new file mode 100644
index 000000000..4fbef8749
--- /dev/null
+++ b/tests/Integration/Api/Client/AccountControllerTest.php
@@ -0,0 +1,162 @@
+create();
+
+ $response = $this->actingAs($user)->get('/api/client/account');
+
+ $response->assertOk()->assertJson([
+ 'object' => 'user',
+ 'attributes' => [
+ 'id' => $user->id,
+ 'admin' => false,
+ 'username' => $user->username,
+ 'email' => $user->email,
+ 'first_name' => $user->name_first,
+ 'last_name' => $user->name_last,
+ 'language' => $user->language,
+ ],
+ ]);
+ }
+
+ /**
+ * Test that the user's email address can be updated via the API.
+ */
+ public function testEmailIsUpdated()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create();
+
+ $response = $this->actingAs($user)->putJson('/api/client/account/email', [
+ 'email' => 'hodor@example.com',
+ 'password' => 'password',
+ ]);
+
+ $response->assertStatus(Response::HTTP_NO_CONTENT);
+
+ $this->assertDatabaseHas('users', ['id' => $user->id, 'email' => 'hodor@example.com']);
+ }
+
+ /**
+ * Tests that an email is not updated if the password provided in the reuqest is not
+ * valid for the account.
+ */
+ public function testEmailIsNotUpdatedWhenPasswordIsInvalid()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create();
+
+ $response = $this->actingAs($user)->putJson('/api/client/account/email', [
+ 'email' => 'hodor@example.com',
+ 'password' => 'invalid',
+ ]);
+
+ $response->assertStatus(Response::HTTP_BAD_REQUEST);
+ $response->assertJsonPath('errors.0.code', 'InvalidPasswordProvidedException');
+ $response->assertJsonPath('errors.0.detail', 'The password provided was invalid for this account.');
+ }
+
+ /**
+ * Tests that an email is not updated if an invalid email address is passed through
+ * in the request.
+ */
+ public function testEmailIsNotUpdatedWhenNotValid()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create();
+
+ $response = $this->actingAs($user)->putJson('/api/client/account/email', [
+ 'email' => '',
+ 'password' => 'password',
+ ]);
+
+ $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
+ $response->assertJsonPath('errors.0.code', 'required');
+ $response->assertJsonPath('errors.0.detail', 'The email field is required.');
+
+ $response = $this->actingAs($user)->putJson('/api/client/account/email', [
+ 'email' => 'invalid',
+ 'password' => 'password',
+ ]);
+
+ $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
+ $response->assertJsonPath('errors.0.code', 'email');
+ $response->assertJsonPath('errors.0.detail', 'The email must be a valid email address.');
+ }
+
+ /**
+ * Test that the password for an account can be successfully updated.
+ */
+ public function testPasswordIsUpdated()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create();
+
+ $mock = Mockery::mock(AuthManager::class);
+ $mock->expects('logoutOtherDevices')->with('New_Password1');
+
+ $this->app->instance(AuthManager::class, $mock);
+
+ $response = $this->actingAs($user)->putJson('/api/client/account/password', [
+ 'current_password' => 'password',
+ 'password' => 'New_Password1',
+ 'password_confirmation' => 'New_Password1',
+ ]);
+
+ $response->assertStatus(Response::HTTP_NO_CONTENT);
+ }
+
+ /**
+ * Test that the password for an account is not updated if the current password is not
+ * provided correctly.
+ */
+ public function testPasswordIsNotUpdatedIfCurrentPasswordIsInvalid()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create();
+
+ $response = $this->actingAs($user)->putJson('/api/client/account/password', [
+ 'current_password' => 'invalid',
+ 'password' => 'New_Password1',
+ 'password_confirmation' => 'New_Password1',
+ ]);
+
+ $response->assertStatus(Response::HTTP_BAD_REQUEST);
+ $response->assertJsonPath('errors.0.code', 'InvalidPasswordProvidedException');
+ $response->assertJsonPath('errors.0.detail', 'The password provided was invalid for this account.');
+ }
+
+ /**
+ * Test that a validation error is returned if the password passed in the request
+ * does not have a confirmation, or the confirmation is not the same as the password.
+ */
+ public function testErrorIsReturnedIfPasswordIsNotConfirmed()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create();
+
+ $response = $this->actingAs($user)->putJson('/api/client/account/password', [
+ 'current_password' => 'password',
+ 'password' => 'New_Password1',
+ 'password_confirmation' => 'Invalid_New_Password',
+ ]);
+
+ $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
+ $response->assertJsonPath('errors.0.code', 'confirmed');
+ $response->assertJsonPath('errors.0.detail', 'The password confirmation does not match.');
+ }
+}
diff --git a/tests/Integration/Api/Client/ApiKeyControllerTest.php b/tests/Integration/Api/Client/ApiKeyControllerTest.php
new file mode 100644
index 000000000..13a0b9c84
--- /dev/null
+++ b/tests/Integration/Api/Client/ApiKeyControllerTest.php
@@ -0,0 +1,219 @@
+forceDelete();
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test that the client's API key can be returned successfully.
+ */
+ public function testApiKeysAreReturned()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create();
+ /** @var \Pterodactyl\Models\ApiKey $key */
+ $key = factory(ApiKey::class)->create([
+ 'user_id' => $user->id,
+ 'key_type' => ApiKey::TYPE_ACCOUNT,
+ ]);
+
+ $response = $this->actingAs($user)->get('/api/client/account/api-keys');
+
+ $response->assertOk();
+ $response->assertJson([
+ 'object' => 'list',
+ 'data' => [
+ [
+ 'object' => 'api_key',
+ 'attributes' => [
+ 'identifier' => $key->identifier,
+ 'description' => $key->memo,
+ 'allowed_ips' => $key->allowed_ips,
+ 'last_used_at' => null,
+ 'created_at' => $key->created_at->toIso8601String(),
+ ],
+ ],
+ ],
+ ]);
+ }
+
+ /**
+ * Test that an API key can be created for the client account. This also checks that the
+ * API key secret is returned as metadata in the response since it will not be returned
+ * after that point.
+ */
+ public function testApiKeyCanBeCreatedForAccount()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create();
+
+ // Small sub-test to ensure we're always comparing the number of keys to the
+ // specific logged in account, and not just the total number of keys stored in
+ // the database.
+ factory(ApiKey::class)->times(10)->create([
+ 'user_id' => factory(User::class)->create()->id,
+ 'key_type' => ApiKey::TYPE_ACCOUNT,
+ ]);
+
+ $response = $this->actingAs($user)->postJson('/api/client/account/api-keys', [
+ 'description' => 'Test Description',
+ 'allowed_ips' => ['127.0.0.1'],
+ ]);
+
+ $response->assertOk();
+
+ /** @var \Pterodactyl\Models\ApiKey $key */
+ $key = ApiKey::query()->where('identifier', $response->json('attributes.identifier'))->firstOrFail();
+
+ $response->assertJson([
+ 'object' => 'api_key',
+ 'attributes' => [
+ 'identifier' => $key->identifier,
+ 'description' => 'Test Description',
+ 'allowed_ips' => ['127.0.0.1'],
+ 'last_used_at' => null,
+ 'created_at' => $key->created_at->toIso8601String(),
+ ],
+ 'meta' => [
+ 'secret_token' => decrypt($key->token),
+ ],
+ ]);
+ }
+
+ /**
+ * Test that no more than 5 API keys can exist at any one time for an account. This prevents
+ * a DoS attack vector against the panel.
+ *
+ * @see https://github.com/pterodactyl/panel/security/advisories/GHSA-pjmh-7xfm-r4x9
+ */
+ public function testNoMoreThanFiveApiKeysCanBeCreatedForAnAccount()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create();
+ factory(ApiKey::class)->times(5)->create([
+ 'user_id' => $user->id,
+ 'key_type' => ApiKey::TYPE_ACCOUNT,
+ ]);
+
+ $response = $this->actingAs($user)->postJson('/api/client/account/api-keys', [
+ 'description' => 'Test Description',
+ 'allowed_ips' => ['127.0.0.1'],
+ ]);
+
+ $response->assertStatus(Response::HTTP_BAD_REQUEST);
+ $response->assertJsonPath('errors.0.code', 'DisplayException');
+ $response->assertJsonPath('errors.0.detail', 'You have reached the account limit for number of API keys.');
+ }
+
+ /**
+ * Test that a bad request results in a validation error being returned by the API.
+ */
+ public function testValidationErrorIsReturnedForBadRequests()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create();
+
+ $response = $this->actingAs($user)->postJson('/api/client/account/api-keys', [
+ 'description' => '',
+ 'allowed_ips' => ['127.0.0.1'],
+ ]);
+
+ $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
+ $response->assertJsonPath('errors.0.code', 'required');
+ $response->assertJsonPath('errors.0.detail', 'The description field is required.');
+ }
+
+ /**
+ * Tests that an API key can be deleted from the account.
+ */
+ public function testApiKeyCanBeDeleted()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create();
+ /** @var \Pterodactyl\Models\ApiKey $key */
+ $key = factory(ApiKey::class)->create([
+ 'user_id' => $user->id,
+ 'key_type' => ApiKey::TYPE_ACCOUNT,
+ ]);
+
+ $response = $this->actingAs($user)->delete('/api/client/account/api-keys/' . $key->identifier);
+ $response->assertStatus(Response::HTTP_NO_CONTENT);
+
+ $this->assertDatabaseMissing('api_keys', ['id' => $key->id]);
+ }
+
+ /**
+ * Test that trying to delete an API key that does not exist results in a 404.
+ */
+ public function testNonExistentApiKeyDeletionReturns404Error()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create();
+ /** @var \Pterodactyl\Models\ApiKey $key */
+ $key = factory(ApiKey::class)->create([
+ 'user_id' => $user->id,
+ 'key_type' => ApiKey::TYPE_ACCOUNT,
+ ]);
+
+ $response = $this->actingAs($user)->delete('/api/client/account/api-keys/1234');
+ $response->assertNotFound();
+
+ $this->assertDatabaseHas('api_keys', ['id' => $key->id]);
+ }
+
+ /**
+ * Test that an API key that exists on the system cannot be deleted if the user
+ * who created it is not the authenticated user.
+ */
+ public function testApiKeyBelongingToAnotherUserCannotBeDeleted()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create();
+ /** @var \Pterodactyl\Models\User $user2 */
+ $user2 = factory(User::class)->create();
+ /** @var \Pterodactyl\Models\ApiKey $key */
+ $key = factory(ApiKey::class)->create([
+ 'user_id' => $user2->id,
+ 'key_type' => ApiKey::TYPE_ACCOUNT,
+ ]);
+
+ $response = $this->actingAs($user)->delete('/api/client/account/api-keys/' . $key->identifier);
+ $response->assertNotFound();
+
+ $this->assertDatabaseHas('api_keys', ['id' => $key->id]);
+ }
+
+ /**
+ * Tests that an application API key also belonging to the logged in user cannot be
+ * deleted through this endpoint if it exists.
+ */
+ public function testApplicationApiKeyCannotBeDeleted()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create();
+ /** @var \Pterodactyl\Models\ApiKey $key */
+ $key = factory(ApiKey::class)->create([
+ 'user_id' => $user->id,
+ 'key_type' => ApiKey::TYPE_APPLICATION,
+ ]);
+
+ $response = $this->actingAs($user)->delete('/api/client/account/api-keys/' . $key->identifier);
+ $response->assertNotFound();
+
+ $this->assertDatabaseHas('api_keys', ['id' => $key->id]);
+ }
+}
diff --git a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php
new file mode 100644
index 000000000..e6dbf0974
--- /dev/null
+++ b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php
@@ -0,0 +1,121 @@
+forceDelete();
+ Node::query()->forceDelete();
+ Location::query()->forceDelete();
+ User::query()->forceDelete();
+
+ parent::tearDown();
+ }
+
+ /**
+ * Setup tests and ensure all of the times are always the same.
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ Carbon::setTestNow(Carbon::now());
+ CarbonImmutable::setTestNow(Carbon::now());
+ }
+
+ /**
+ * Returns a link to the specific resource using the client API.
+ *
+ * @param mixed $model
+ * @param string|null $append
+ * @return string
+ */
+ protected function link($model, $append = null): string
+ {
+ Assert::isInstanceOfAny($model, [Server::class, Schedule::class, Task::class]);
+
+ $link = '';
+ switch (get_class($model)) {
+ case Server::class:
+ $link = "/api/client/servers/{$model->uuid}";
+ break;
+ case Schedule::class:
+ $link = "/api/client/servers/{$model->server->uuid}/schedules/{$model->id}";
+ break;
+ case Task::class:
+ $link = "/api/client/servers/{$model->schedule->server->uuid}/schedules/{$model->schedule->id}/tasks/{$model->id}";
+ break;
+ }
+
+ return $link . ($append ? '/' . ltrim($append, '/') : '');
+ }
+
+ /**
+ * Generates a user and a server for that user. If an array of permissions is passed it
+ * is assumed that the user is actually a subuser of the server.
+ *
+ * @param string[] $permissions
+ * @return array
+ */
+ protected function generateTestAccount(array $permissions = []): array
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create();
+
+ if (empty($permissions)) {
+ return [$user, $this->createServerModel(['user_id' => $user->id])];
+ }
+
+ $server = $this->createServerModel();
+
+ Subuser::query()->create([
+ 'user_id' => $user->id,
+ 'server_id' => $server->id,
+ 'permissions' => $permissions,
+ ]);
+
+ return [$user, $server];
+ }
+
+ /**
+ * Asserts that the data passed through matches the output of the data from the transformer. This
+ * will remove the "relationships" key when performing the comparison.
+ *
+ * @param array $data
+ * @param \Pterodactyl\Models\Model|\Illuminate\Database\Eloquent\Model $model
+ */
+ protected function assertJsonTransformedWith(array $data, $model)
+ {
+ $reflect = new ReflectionClass($model);
+ $transformer = sprintf('\\Pterodactyl\\Transformers\\Api\\Client\\%sTransformer', $reflect->getShortName());
+
+ $transformer = new $transformer;
+ $this->assertInstanceOf(BaseClientTransformer::class, $transformer);
+
+ $this->assertSame(
+ $transformer->transform($model),
+ Collection::make($data)->except(['relationships'])->toArray()
+ );
+ }
+}
diff --git a/tests/Integration/Api/Client/ClientControllerTest.php b/tests/Integration/Api/Client/ClientControllerTest.php
new file mode 100644
index 000000000..1561f59cf
--- /dev/null
+++ b/tests/Integration/Api/Client/ClientControllerTest.php
@@ -0,0 +1,146 @@
+times(3)->create();
+
+ /** @var \Pterodactyl\Models\Server[] $servers */
+ $servers = [
+ $this->createServerModel(['user_id' => $users[0]->id]),
+ $this->createServerModel(['user_id' => $users[1]->id]),
+ $this->createServerModel(['user_id' => $users[2]->id]),
+ ];
+
+ $response = $this->actingAs($users[0])->getJson('/api/client');
+
+ $response->assertOk();
+ $response->assertJsonPath('object', 'list');
+ $response->assertJsonPath('data.0.object', Server::RESOURCE_NAME);
+ $response->assertJsonPath('data.0.attributes.identifier', $servers[0]->uuidShort);
+ $response->assertJsonPath('data.0.attributes.server_owner', true);
+ $response->assertJsonPath('meta.pagination.total', 1);
+ $response->assertJsonPath('meta.pagination.per_page', config('pterodactyl.paginate.frontend.servers'));
+ }
+
+ /**
+ * Tests that all of the servers on the system are returned when making the request as an
+ * administrator and including the ?filter=all parameter in the URL.
+ */
+ public function testFilterIncludeAllServersWhenAdministrator()
+ {
+ /** @var \Pterodactyl\Models\User[] $users */
+ $users = factory(User::class)->times(3)->create();
+ $users[0]->root_admin = true;
+
+ $servers = [
+ $this->createServerModel(['user_id' => $users[0]->id]),
+ $this->createServerModel(['user_id' => $users[1]->id]),
+ $this->createServerModel(['user_id' => $users[2]->id]),
+ ];
+
+ $response = $this->actingAs($users[0])->getJson('/api/client?filter=all');
+
+ $response->assertOk();
+ $response->assertJsonCount(3, 'data');
+
+ for ($i = 0; $i < 3; $i++) {
+ $response->assertJsonPath("data.{$i}.attributes.server_owner", $i === 0);
+ $response->assertJsonPath("data.{$i}.attributes.identifier", $servers[$i]->uuidShort);
+ }
+ }
+
+ /**
+ * Test that servers where the user is a subuser are returned by default in the API call.
+ */
+ public function testServersUserIsASubuserOfAreReturned()
+ {
+ /** @var \Pterodactyl\Models\User[] $users */
+ $users = factory(User::class)->times(3)->create();
+ $servers = [
+ $this->createServerModel(['user_id' => $users[0]->id]),
+ $this->createServerModel(['user_id' => $users[1]->id]),
+ $this->createServerModel(['user_id' => $users[2]->id]),
+ ];
+
+ // Set user 0 as a subuser of server 1. Thus, we should get two servers
+ // back in the response when making the API call as user 0.
+ Subuser::query()->create([
+ 'user_id' => $users[0]->id,
+ 'server_id' => $servers[1]->id,
+ 'permissions' => [Permission::ACTION_WEBSOCKET_CONNECT],
+ ]);
+
+ $response = $this->actingAs($users[0])->getJson('/api/client');
+
+ $response->assertOk();
+ $response->assertJsonCount(2, 'data');
+ $response->assertJsonPath('data.0.attributes.server_owner', true);
+ $response->assertJsonPath('data.0.attributes.identifier', $servers[0]->uuidShort);
+ $response->assertJsonPath('data.1.attributes.server_owner', false);
+ $response->assertJsonPath('data.1.attributes.identifier', $servers[1]->uuidShort);
+ }
+
+ /**
+ * Returns only servers that the user owns, not servers they are a subuser of.
+ */
+ public function testFilterOnlyOwnerServers()
+ {
+ /** @var \Pterodactyl\Models\User[] $users */
+ $users = factory(User::class)->times(3)->create();
+ $servers = [
+ $this->createServerModel(['user_id' => $users[0]->id]),
+ $this->createServerModel(['user_id' => $users[1]->id]),
+ $this->createServerModel(['user_id' => $users[2]->id]),
+ ];
+
+ // Set user 0 as a subuser of server 1. Thus, we should get two servers
+ // back in the response when making the API call as user 0.
+ Subuser::query()->create([
+ 'user_id' => $users[0]->id,
+ 'server_id' => $servers[1]->id,
+ 'permissions' => [Permission::ACTION_WEBSOCKET_CONNECT],
+ ]);
+
+ $response = $this->actingAs($users[0])->getJson('/api/client?filter=owner');
+
+ $response->assertOk();
+ $response->assertJsonCount(1, 'data');
+ $response->assertJsonPath('data.0.attributes.server_owner', true);
+ $response->assertJsonPath('data.0.attributes.identifier', $servers[0]->uuidShort);
+ }
+
+ /**
+ * Tests that the permissions from the Panel are returned correctly.
+ */
+ public function testPermissionsAreReturned()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create();
+
+ $this->actingAs($user)
+ ->getJson('/api/client/permissions')
+ ->assertOk()
+ ->assertJson([
+ 'object' => 'system_permissions',
+ 'attributes' => [
+ 'permissions' => Permission::permissions()->toArray(),
+ ],
+ ]);
+ }
+}
diff --git a/tests/Integration/Api/Client/Server/CommandControllerTest.php b/tests/Integration/Api/Client/Server/CommandControllerTest.php
new file mode 100644
index 000000000..3d7cd090f
--- /dev/null
+++ b/tests/Integration/Api/Client/Server/CommandControllerTest.php
@@ -0,0 +1,100 @@
+repository = Mockery::mock(DaemonCommandRepository::class);
+ $this->app->instance(DaemonCommandRepository::class, $this->repository);
+ }
+
+ /**
+ * Test that a validation error is returned if there is no command present in the
+ * request.
+ */
+ public function testValidationErrorIsReturnedIfNoCommandIsPresent()
+ {
+ [$user, $server] = $this->generateTestAccount();
+
+ $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/command", [
+ 'command' => '',
+ ]);
+
+ $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
+ $response->assertJsonPath('errors.0.code', 'required');
+ }
+
+ /**
+ * Test that a subuser without the required permission receives an error when trying to
+ * execute the command.
+ */
+ public function testSubuserWithoutPermissionReceivesError()
+ {
+ [$user, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]);
+
+ $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/command", [
+ 'command' => 'say Test',
+ ]);
+
+ $response->assertStatus(Response::HTTP_FORBIDDEN);
+ }
+
+ /**
+ * Test that a command can be sent to the server.
+ */
+ public function testCommandCanSendToServer()
+ {
+ [$user, $server] = $this->generateTestAccount([Permission::ACTION_CONTROL_CONSOLE]);
+
+ $this->repository->expects('setServer')->with(Mockery::on(function ($value) use ($server) {
+ return $value->uuid === $server->uuid;
+ }))->andReturnSelf();
+ $this->repository->expects('send')->with('say Test')->andReturn(new GuzzleResponse);
+
+ $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/command", [
+ 'command' => 'say Test',
+ ]);
+
+ $response->assertStatus(Response::HTTP_NO_CONTENT);
+ }
+
+ /**
+ * Test that an error is returned when the server is offline that is more specific than the
+ * regular daemon connection error.
+ */
+ public function testErrorIsReturnedWhenServerIsOffline()
+ {
+ [$user, $server] = $this->generateTestAccount();
+
+ $this->repository->expects('setServer->send')->andThrows(
+ new BadResponseException('', new Request('GET', 'test'), new GuzzleResponse(Response::HTTP_BAD_GATEWAY))
+ );
+
+ $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/command", [
+ 'command' => 'say Test',
+ ]);
+
+ $response->assertStatus(Response::HTTP_BAD_GATEWAY);
+ $response->assertJsonPath('errors.0.code', 'HttpException');
+ $response->assertJsonPath('errors.0.detail', 'Server must be online in order to send commands.');
+ }
+}
diff --git a/tests/Integration/Api/Client/Server/PowerControllerTest.php b/tests/Integration/Api/Client/Server/PowerControllerTest.php
new file mode 100644
index 000000000..80f58010a
--- /dev/null
+++ b/tests/Integration/Api/Client/Server/PowerControllerTest.php
@@ -0,0 +1,107 @@
+generateTestAccount($permissions);
+
+ $this->actingAs($user)
+ ->postJson("/api/client/servers/{$server->uuid}/power", ['signal' => $action])
+ ->assertStatus(Response::HTTP_FORBIDDEN);
+ }
+
+ /**
+ * Test that sending an invalid power signal returns an error.
+ */
+ public function testInvalidPowerSignalResultsInError()
+ {
+ [$user, $server] = $this->generateTestAccount();
+
+ $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/power", [
+ 'signal' => 'invalid',
+ ]);
+
+ $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
+ $response->assertJsonPath('errors.0.code', 'in');
+ $response->assertJsonPath('errors.0.detail', 'The selected signal is invalid.');
+ }
+
+ /**
+ * Test that sending a valid power actions works.
+ *
+ * @param string $action
+ * @param string $permission
+ * @dataProvider validPowerActionDataProvider
+ */
+ public function testActionCanBeSentToServer(string $action, string $permission)
+ {
+ $service = Mockery::mock(DaemonPowerRepository::class);
+ $this->app->instance(DaemonPowerRepository::class, $service);
+
+ [$user, $server] = $this->generateTestAccount([$permission]);
+
+ $service->expects('setServer')
+ ->with(Mockery::on(function ($value) use ($server) {
+ return $server->uuid === $value->uuid;
+ }))
+ ->andReturnSelf()
+ ->getMock()
+ ->expects('send')
+ ->with(trim($action));
+
+ $this->actingAs($user)
+ ->postJson("/api/client/servers/{$server->uuid}/power", ['signal' => $action])
+ ->assertStatus(Response::HTTP_NO_CONTENT);
+ }
+
+ /**
+ * Returns invalid permission combinations for a given power action.
+ *
+ * @return array
+ */
+ public function invalidPermissionDataProvider(): array
+ {
+ return [
+ ['start', [Permission::ACTION_CONTROL_STOP, Permission::ACTION_CONTROL_RESTART]],
+ ['stop', [Permission::ACTION_CONTROL_START]],
+ ['kill', [Permission::ACTION_CONTROL_START, Permission::ACTION_CONTROL_RESTART]],
+ ['restart', [Permission::ACTION_CONTROL_STOP, Permission::ACTION_CONTROL_START]],
+ ['random', [Permission::ACTION_CONTROL_START]],
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ public function validPowerActionDataProvider(): array
+ {
+ return [
+ ['start', Permission::ACTION_CONTROL_START],
+ ['stop', Permission::ACTION_CONTROL_STOP],
+ ['restart', Permission::ACTION_CONTROL_RESTART],
+ ['kill', Permission::ACTION_CONTROL_STOP],
+ // Yes, these spaces are intentional. You should be able to send values with or without
+ // a space on the start/end since we should be trimming the values.
+ [' restart', Permission::ACTION_CONTROL_RESTART],
+ ['kill ', Permission::ACTION_CONTROL_STOP],
+ ];
+ }
+}
diff --git a/tests/Integration/Api/Client/Server/ResourceUtilitizationControllerTest.php b/tests/Integration/Api/Client/Server/ResourceUtilitizationControllerTest.php
new file mode 100644
index 000000000..7c713dd33
--- /dev/null
+++ b/tests/Integration/Api/Client/Server/ResourceUtilitizationControllerTest.php
@@ -0,0 +1,44 @@
+app->instance(DaemonServerRepository::class, $service);
+
+ [$user, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]);
+
+ $service->expects('setServer')->with(Mockery::on(function ($value) use ($server) {
+ return $server->uuid === $value->uuid;
+ }))->andReturnSelf()->getMock()->expects('getDetails')->andReturns([]);
+
+ $response = $this->actingAs($user)->getJson("/api/client/servers/{$server->uuid}/resources");
+
+ $response->assertOk();
+ $response->assertJson([
+ 'object' => 'stats',
+ 'attributes' => [
+ 'current_state' => 'stopped',
+ 'is_suspended' => false,
+ 'resources' => [
+ 'memory_bytes' => 0,
+ 'cpu_absolute' => 0,
+ 'disk_bytes' => 0,
+ 'network_rx_bytes' => 0,
+ 'network_tx_bytes' => 0,
+ ],
+ ],
+ ]);
+ }
+}
diff --git a/tests/Integration/Api/Client/Server/Schedule/CreateServerScheduleTest.php b/tests/Integration/Api/Client/Server/Schedule/CreateServerScheduleTest.php
new file mode 100644
index 000000000..5b45e7fe7
--- /dev/null
+++ b/tests/Integration/Api/Client/Server/Schedule/CreateServerScheduleTest.php
@@ -0,0 +1,96 @@
+generateTestAccount($permissions);
+
+ $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/schedules", [
+ 'name' => 'Test Schedule',
+ 'is_active' => false,
+ 'minute' => '0',
+ 'hour' => '*/2',
+ 'day_of_week' => '2',
+ 'day_of_month' => '*',
+ ]);
+
+ $response->assertOk();
+
+ $this->assertNotNull($id = $response->json('attributes.id'));
+
+ /** @var \Pterodactyl\Models\Schedule $schedule */
+ $schedule = Schedule::query()->findOrFail($id);
+ $this->assertFalse($schedule->is_active);
+ $this->assertFalse($schedule->is_processing);
+ $this->assertSame('0', $schedule->cron_minute);
+ $this->assertSame('*/2', $schedule->cron_hour);
+ $this->assertSame('2', $schedule->cron_day_of_week);
+ $this->assertSame('*', $schedule->cron_day_of_month);
+ $this->assertSame('Test Schedule', $schedule->name);
+
+ $this->assertJsonTransformedWith($response->json('attributes'), $schedule);
+ $response->assertJsonCount(0, 'attributes.relationships.tasks.data');
+ }
+
+ /**
+ * Test that the validation rules for scheduling work as expected.
+ */
+ public function testScheduleValidationRules()
+ {
+ [$user, $server] = $this->generateTestAccount();
+
+ $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/schedules", []);
+
+ $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
+ foreach (['name', 'minute', 'hour', 'day_of_month', 'day_of_week'] as $i => $field) {
+ $response->assertJsonPath("errors.{$i}.code", 'required');
+ $response->assertJsonPath("errors.{$i}.source.field", $field);
+ }
+
+ $this->actingAs($user)
+ ->postJson("/api/client/servers/{$server->uuid}/schedules", [
+ 'name' => 'Testing',
+ 'is_active' => 'no',
+ 'minute' => '*',
+ 'hour' => '*',
+ 'day_of_month' => '*',
+ 'day_of_week' => '*',
+ ])
+ ->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)
+ ->assertJsonPath('errors.0.code', 'boolean');
+ }
+
+ /**
+ * Test that a subuser without required permissions cannot create a schedule.
+ */
+ public function testSubuserCannotCreateScheduleWithoutPermissions()
+ {
+ [$user, $server] = $this->generateTestAccount([Permission::ACTION_SCHEDULE_UPDATE]);
+
+ $this->actingAs($user)
+ ->postJson("/api/client/servers/{$server->uuid}/schedules", [])
+ ->assertForbidden();
+ }
+
+ /**
+ * @return array
+ */
+ public function permissionsDataProvider(): array
+ {
+ return [[[]], [[Permission::ACTION_SCHEDULE_CREATE]]];
+ }
+}
diff --git a/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php b/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php
new file mode 100644
index 000000000..cff7591ba
--- /dev/null
+++ b/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php
@@ -0,0 +1,88 @@
+generateTestAccount($permissions);
+
+ $schedule = factory(Schedule::class)->create(['server_id' => $server->id]);
+ $task = factory(Task::class)->create(['schedule_id' => $schedule->id]);
+
+ $this->actingAs($user)
+ ->deleteJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}")
+ ->assertStatus(Response::HTTP_NO_CONTENT);
+
+ $this->assertDatabaseMissing('schedules', ['id' => $schedule->id]);
+ $this->assertDatabaseMissing('tasks', ['id' => $task->id]);
+ }
+
+ /**
+ * Test that no error is returned if the schedule does not exist on the system at all.
+ */
+ public function testNotFoundErrorIsReturnedIfScheduleDoesNotExistAtAll()
+ {
+ [$user, $server] = $this->generateTestAccount();
+
+ $this->actingAs($user)
+ ->deleteJson("/api/client/servers/{$server->uuid}/schedules/123456789")
+ ->assertStatus(Response::HTTP_NOT_FOUND);
+ }
+
+ /**
+ * Ensure that a schedule belonging to another server cannot be deleted and its presence is not
+ * revealed to the user.
+ */
+ public function testNotFoundErrorIsReturnedIfScheduleDoesNotBelongToServer()
+ {
+ [$user, $server] = $this->generateTestAccount();
+ [, $server2] = $this->generateTestAccount(['user_id' => $user->id]);
+
+ $schedule = factory(Schedule::class)->create(['server_id' => $server2->id]);
+
+ $this->actingAs($user)
+ ->deleteJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}")
+ ->assertStatus(Response::HTTP_NOT_FOUND);
+
+ $this->assertDatabaseHas('schedules', ['id' => $schedule->id]);
+ }
+
+ /**
+ * Test that an error is returned if the subuser does not have the required permissions to
+ * delete the schedule from the server.
+ */
+ public function testErrorIsReturnedIfSubuserDoesNotHaveRequiredPermissions()
+ {
+ [$user, $server] = $this->generateTestAccount([Permission::ACTION_SCHEDULE_UPDATE]);
+
+ $schedule = factory(Schedule::class)->create(['server_id' => $server->id]);
+
+ $this->actingAs($user)
+ ->deleteJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}")
+ ->assertStatus(Response::HTTP_FORBIDDEN);
+
+ $this->assertDatabaseHas('schedules', ['id' => $schedule->id]);
+ }
+
+ /**
+ * @return array
+ */
+ public function permissionsDataProvider(): array
+ {
+ return [[[]], [[Permission::ACTION_SCHEDULE_DELETE]]];
+ }
+}
diff --git a/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php b/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php
new file mode 100644
index 000000000..67be3fd84
--- /dev/null
+++ b/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php
@@ -0,0 +1,106 @@
+forceDelete();
+ Schedule::query()->forceDelete();
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test that schedules for a server are returned.
+ *
+ * @param array $permissions
+ * @param bool $individual
+ * @dataProvider permissionsDataProvider
+ */
+ public function testServerSchedulesAreReturned($permissions, $individual)
+ {
+ [$user, $server] = $this->generateTestAccount($permissions);
+
+ /** @var \Pterodactyl\Models\Schedule $schedule */
+ $schedule = factory(Schedule::class)->create(['server_id' => $server->id]);
+ /** @var \Pterodactyl\Models\Task $task */
+ $task = factory(Task::class)->create(['schedule_id' => $schedule->id, 'sequence_id' => 1, 'time_offset' => 0]);
+
+ $response = $this->actingAs($user)
+ ->getJson(
+ $individual
+ ? "/api/client/servers/{$server->uuid}/schedules/{$schedule->id}"
+ : "/api/client/servers/{$server->uuid}/schedules"
+ )
+ ->assertOk();
+
+ $prefix = $individual ? '' : 'data.0.';
+ if (! $individual) {
+ $response->assertJsonCount(1, 'data');
+ }
+
+ $response->assertJsonCount(1, $prefix . 'attributes.relationships.tasks.data');
+
+ $response->assertJsonPath($prefix . 'object', Schedule::RESOURCE_NAME);
+ $response->assertJsonPath($prefix . 'attributes.relationships.tasks.data.0.object', Task::RESOURCE_NAME);
+
+ $this->assertJsonTransformedWith($response->json($prefix . 'attributes'), $schedule);
+ $this->assertJsonTransformedWith($response->json($prefix . 'attributes.relationships.tasks.data.0.attributes'), $task);
+ }
+
+ /**
+ * Test that a schedule belonging to another server cannot be viewed.
+ */
+ public function testScheduleBelongingToAnotherServerCannotBeViewed()
+ {
+ [$user, $server] = $this->generateTestAccount();
+ [, $server2] = $this->generateTestAccount(['user_id' => $user->id]);
+
+ $schedule = factory(Schedule::class)->create(['server_id' => $server2->id]);
+
+ $this->actingAs($user)
+ ->getJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}")
+ ->assertNotFound();
+ }
+
+ /**
+ * Test that a subuser without the required permissions is unable to access the schedules endpoint.
+ */
+ public function testUserWithoutPermissionCannotViewSchedules()
+ {
+ [$user, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]);
+
+ $this->actingAs($user)
+ ->getJson("/api/client/servers/{$server->uuid}/schedules")
+ ->assertForbidden();
+
+ $schedule = factory(Schedule::class)->create(['server_id' => $server->id]);
+
+ $this->actingAs($user)
+ ->getJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}")
+ ->assertForbidden();
+ }
+
+ /**
+ * @return array
+ */
+ public function permissionsDataProvider(): array
+ {
+ return [
+ [[], false],
+ [[], true],
+ [[Permission::ACTION_SCHEDULE_READ], false],
+ [[Permission::ACTION_SCHEDULE_READ], true],
+ ];
+ }
+}
diff --git a/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php b/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php
new file mode 100644
index 000000000..d2d3132ff
--- /dev/null
+++ b/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php
@@ -0,0 +1,84 @@
+generateTestAccount($permissions);
+
+ /** @var \Pterodactyl\Models\Schedule $schedule */
+ $schedule = factory(Schedule::class)->create(['server_id' => $server->id]);
+ $expected = Utilities::getScheduleNextRunDate('5', '*', '*', '*');
+
+ $response = $this->actingAs($user)
+ ->postJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}", [
+ 'name' => 'Updated Schedule Name',
+ 'minute' => '5',
+ 'hour' => '*',
+ 'day_of_week' => '*',
+ 'day_of_month' => '*',
+ 'is_active' => false,
+ ]);
+
+ $schedule = $schedule->refresh();
+
+ $response->assertOk();
+ $this->assertSame('Updated Schedule Name', $schedule->name);
+ $this->assertFalse($schedule->is_active);
+ $this->assertJsonTransformedWith($response->json('attributes'), $schedule);
+
+ $this->assertSame($expected->toIso8601String(), $schedule->next_run_at->toIso8601String());
+ }
+
+ /**
+ * Test that an error is returned if the schedule exists but does not belong to this
+ * specific server instance.
+ */
+ public function testErrorIsReturnedIfScheduleDoesNotBelongToServer()
+ {
+ [$user, $server] = $this->generateTestAccount();
+ [, $server2] = $this->generateTestAccount(['user_id' => $user->id]);
+
+ $schedule = factory(Schedule::class)->create(['server_id' => $server2->id]);
+
+ $this->actingAs($user)
+ ->postJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}")
+ ->assertNotFound();
+ }
+
+ /**
+ * Test that an error is returned if the subuser does not have permission to modify a
+ * server schedule.
+ */
+ public function testErrorIsReturnedIfSubuserDoesNotHavePermissionToModifySchedule()
+ {
+ [$user, $server] = $this->generateTestAccount([Permission::ACTION_SCHEDULE_CREATE]);
+
+ $schedule = factory(Schedule::class)->create(['server_id' => $server->id]);
+
+ $this->actingAs($user)
+ ->postJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}")
+ ->assertForbidden();
+ }
+
+ /**
+ * @return array
+ */
+ public function permissionsDataProvider(): array
+ {
+ return [[[]], [[Permission::ACTION_SCHEDULE_UPDATE]]];
+ }
+}
diff --git a/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php b/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php
new file mode 100644
index 000000000..2044221e5
--- /dev/null
+++ b/tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php
@@ -0,0 +1,177 @@
+generateTestAccount($permissions);
+
+ /** @var \Pterodactyl\Models\Schedule $schedule */
+ $schedule = factory(Schedule::class)->create(['server_id' => $server->id]);
+ $this->assertEmpty($schedule->tasks);
+
+ $response = $this->actingAs($user)->postJson($this->link($schedule, '/tasks'), [
+ 'action' => 'command',
+ 'payload' => 'say Test',
+ 'time_offset' => 10,
+ 'sequence_id' => 1,
+ ]);
+
+ $response->assertOk();
+ /** @var \Pterodactyl\Models\Task $task */
+ $task = Task::query()->findOrFail($response->json('attributes.id'));
+
+ $this->assertSame($schedule->id, $task->schedule_id);
+ $this->assertSame(1, $task->sequence_id);
+ $this->assertSame('command', $task->action);
+ $this->assertSame('say Test', $task->payload);
+ $this->assertSame(10, $task->time_offset);
+ $this->assertJsonTransformedWith($response->json('attributes'), $task);
+ }
+
+ /**
+ * Test that validation errors are returned correctly if bad data is passed into the API.
+ */
+ public function testValidationErrorsAreReturned()
+ {
+ [$user, $server] = $this->generateTestAccount();
+
+ /** @var \Pterodactyl\Models\Schedule $schedule */
+ $schedule = factory(Schedule::class)->create(['server_id' => $server->id]);
+
+ $response = $this->actingAs($user)->postJson($this->link($schedule, '/tasks'))->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
+
+ foreach (['action', 'payload', 'time_offset'] as $i => $field) {
+ $response->assertJsonPath("errors.{$i}.code", $field === 'payload' ? 'required_unless' : 'required');
+ $response->assertJsonPath("errors.{$i}.source.field", $field);
+ }
+
+ $this->actingAs($user)->postJson($this->link($schedule, '/tasks'), [
+ 'action' => 'hodor',
+ 'payload' => 'say Test',
+ 'time_offset' => 0,
+ ])
+ ->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)
+ ->assertJsonPath('errors.0.code', 'in')
+ ->assertJsonPath('errors.0.source.field', 'action');
+
+ $this->actingAs($user)->postJson($this->link($schedule, '/tasks'), [
+ 'action' => 'command',
+ 'time_offset' => 0,
+ ])
+ ->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)
+ ->assertJsonPath('errors.0.code', 'required_unless')
+ ->assertJsonPath('errors.0.source.field', 'payload');
+
+ $this->actingAs($user)->postJson($this->link($schedule, '/tasks'), [
+ 'action' => 'command',
+ 'payload' => 'say Test',
+ 'time_offset' => 0,
+ 'sequence_id' => 'hodor',
+ ])
+ ->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)
+ ->assertJsonPath('errors.0.code', 'numeric')
+ ->assertJsonPath('errors.0.source.field', 'sequence_id');
+ }
+
+ /**
+ * Test that backups can be tasked out correctly since they do not require a payload.
+ */
+ public function testBackupsCanBeTaskedCorrectly()
+ {
+ [$user, $server] = $this->generateTestAccount();
+
+ /** @var \Pterodactyl\Models\Schedule $schedule */
+ $schedule = factory(Schedule::class)->create(['server_id' => $server->id]);
+
+ $this->actingAs($user)->postJson($this->link($schedule, '/tasks'), [
+ 'action' => 'backup',
+ 'time_offset' => 0,
+ ])->assertOk();
+
+ $this->actingAs($user)->postJson($this->link($schedule, '/tasks'), [
+ 'action' => 'backup',
+ 'payload' => "file.txt\nfile2.log",
+ 'time_offset' => 0,
+ ])->assertOk();
+ }
+
+ /**
+ * Test that an error is returned if the user attempts to create an additional task that
+ * would put the schedule over the task limit.
+ */
+ public function testErrorIsReturnedIfTooManyTasksExistForSchedule()
+ {
+ config()->set('pterodactyl.client_features.schedules.per_schedule_task_limit', 2);
+
+ [$user, $server] = $this->generateTestAccount();
+
+ /** @var \Pterodactyl\Models\Schedule $schedule */
+ $schedule = factory(Schedule::class)->create(['server_id' => $server->id]);
+ factory(Task::class)->times(2)->create(['schedule_id' => $schedule->id]);
+
+ $this->actingAs($user)->postJson($this->link($schedule, '/tasks'), [
+ 'action' => 'command',
+ 'payload' => 'say test',
+ 'time_offset' => 0,
+ ])
+ ->assertStatus(Response::HTTP_BAD_REQUEST)
+ ->assertJsonPath('errors.0.code', 'ServiceLimitExceededException')
+ ->assertJsonPath('errors.0.detail', 'Schedules may not have more than 2 tasks associated with them. Creating this task would put this schedule over the limit.');
+ }
+
+ /**
+ * Test that an error is returned if the targeted schedule does not belong to the server
+ * in the request.
+ */
+ public function testErrorIsReturnedIfScheduleDoesNotBelongToServer()
+ {
+ [$user, $server] = $this->generateTestAccount();
+ [, $server2] = $this->generateTestAccount(['user_id' => $user->id]);
+
+ /** @var \Pterodactyl\Models\Schedule $schedule */
+ $schedule = factory(Schedule::class)->create(['server_id' => $server2->id]);
+
+ $this->actingAs($user)
+ ->postJson("/api/client/servers/{$server->uuid}/schedules/{$schedule->id}/tasks")
+ ->assertNotFound();
+ }
+
+ /**
+ * Test that an error is returned if the subuser making the request does not have permission
+ * to update a schedule.
+ */
+ public function testErrorIsReturnedIfSubuserDoesNotHaveScheduleUpdatePermissions()
+ {
+ [$user, $server] = $this->generateTestAccount([Permission::ACTION_SCHEDULE_CREATE]);
+
+ /** @var \Pterodactyl\Models\Schedule $schedule */
+ $schedule = factory(Schedule::class)->create(['server_id' => $server->id]);
+
+ $this->actingAs($user)
+ ->postJson($this->link($schedule, '/tasks'))
+ ->assertForbidden();
+ }
+
+ /**
+ * @return array
+ */
+ public function permissionsDataProvider(): array
+ {
+ return [[[]], [[Permission::ACTION_SCHEDULE_UPDATE]]];
+ }
+}
diff --git a/tests/Integration/Api/Client/Server/SettingsControllerTest.php b/tests/Integration/Api/Client/Server/SettingsControllerTest.php
new file mode 100644
index 000000000..99c32a56a
--- /dev/null
+++ b/tests/Integration/Api/Client/Server/SettingsControllerTest.php
@@ -0,0 +1,128 @@
+generateTestAccount($permissions);
+ $originalName = $server->name;
+
+ $response = $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/settings/rename", [
+ 'name' => '',
+ ]);
+
+ $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
+ $response->assertJsonPath('errors.0.code', 'required');
+
+ $server = $server->refresh();
+ $this->assertSame($originalName, $server->name);
+
+ $this->actingAs($user)
+ ->postJson("/api/client/servers/{$server->uuid}/settings/rename", [
+ 'name' => 'Test Server Name',
+ ])
+ ->assertStatus(Response::HTTP_NO_CONTENT);
+
+ $server = $server->refresh();
+ $this->assertSame('Test Server Name', $server->name);
+ }
+
+ /**
+ * Test that a subuser receives a permissions error if they do not have the required permission
+ * and attempt to change the name.
+ */
+ public function testSubuserCannotChangeServerNameWithoutPermission()
+ {
+ [$user, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]);
+ $originalName = $server->name;
+
+ $this->actingAs($user)
+ ->postJson("/api/client/servers/{$server->uuid}/settings/rename", [
+ 'name' => 'Test Server Name',
+ ])
+ ->assertStatus(Response::HTTP_FORBIDDEN);
+
+ $server = $server->refresh();
+ $this->assertSame($originalName, $server->name);
+ }
+
+ /**
+ * Test that a server can be reinstalled. Honestly this test doesn't do much of anything other
+ * than make sure the endpoint works since.
+ *
+ * @param array $permissions
+ * @dataProvider reinstallPermissionsDataProvider
+ */
+ public function testServerCanBeReinstalled($permissions)
+ {
+ /** @var \Pterodactyl\Models\Server $server */
+ [$user, $server] = $this->generateTestAccount($permissions);
+ $this->assertSame(Server::STATUS_INSTALLED, $server->installed);
+
+ $service = Mockery::mock(DaemonServerRepository::class);
+ $this->app->instance(DaemonServerRepository::class, $service);
+
+ $service->expects('setServer')
+ ->with(Mockery::on(function ($value) use ($server) {
+ return $value->uuid === $server->uuid;
+ }))
+ ->andReturnSelf()
+ ->getMock()
+ ->expects('reinstall')
+ ->andReturnUndefined();
+
+ $this->actingAs($user)->postJson("/api/client/servers/{$server->uuid}/settings/reinstall")
+ ->assertStatus(Response::HTTP_ACCEPTED);
+
+ $server = $server->refresh();
+ $this->assertSame(Server::STATUS_INSTALLING, $server->installed);
+ }
+
+ /**
+ * Test that a subuser receives a permissions error if they do not have the required permission
+ * and attempt to reinstall a server.
+ */
+ public function testSubuserCannotReinstallServerWithoutPermission()
+ {
+ [$user, $server] = $this->generateTestAccount([Permission::ACTION_WEBSOCKET_CONNECT]);
+
+ $this->actingAs($user)
+ ->postJson("/api/client/servers/{$server->uuid}/settings/reinstall")
+ ->assertStatus(Response::HTTP_FORBIDDEN);
+
+ $server = $server->refresh();
+ $this->assertSame(Server::STATUS_INSTALLED, $server->installed);
+ }
+
+ /**
+ * @return array
+ */
+ public function renamePermissionsDataProvider(): array
+ {
+ return [[[]], [[Permission::ACTION_SETTINGS_RENAME]]];
+ }
+
+ /**
+ * @return array
+ */
+ public function reinstallPermissionsDataProvider(): array
+ {
+ return [[[]], [[Permission::ACTION_SETTINGS_REINSTALL]]];
+ }
+}
diff --git a/tests/Integration/Api/Client/Server/WebsocketControllerTest.php b/tests/Integration/Api/Client/Server/WebsocketControllerTest.php
new file mode 100644
index 000000000..0d8851d3a
--- /dev/null
+++ b/tests/Integration/Api/Client/Server/WebsocketControllerTest.php
@@ -0,0 +1,98 @@
+generateTestAccount([Permission::ACTION_CONTROL_RESTART]);
+
+ $this->actingAs($user)->getJson("/api/client/servers/{$server->uuid}/websocket")
+ ->assertStatus(Response::HTTP_FORBIDDEN)
+ ->assertJsonPath('errors.0.code', 'HttpException')
+ ->assertJsonPath('errors.0.detail', 'You do not have permission to connect to this server\'s websocket.');
+ }
+
+ /**
+ * Test that the expected permissions are returned for the server owner and that the JWT is
+ * configured correctly.
+ */
+ public function testJwtAndWebsocketUrlAreReturnedForServerOwner()
+ {
+ CarbonImmutable::setTestNow(Carbon::now());
+
+ /** @var \Pterodactyl\Models\User $user */
+ /** @var \Pterodactyl\Models\Server $server */
+ [$user, $server] = $this->generateTestAccount();
+
+ // Force the node to HTTPS since we want to confirm it gets transformed to wss:// in the URL.
+ $server->node->scheme = 'https';
+ $server->node->save();
+
+ $response = $this->actingAs($user)->getJson("/api/client/servers/{$server->uuid}/websocket");
+
+ $response->assertOk();
+ $response->assertJsonStructure(['data' => ['token', 'socket']]);
+
+ $connection = $response->json('data.socket');
+ $this->assertStringStartsWith('wss://', $connection, 'Failed asserting that websocket connection address has expected "wss://" prefix.');
+ $this->assertStringEndsWith("/api/servers/{$server->uuid}/ws", $connection, 'Failed asserting that websocket connection address uses expected Wings endpoint.');
+
+ $token = (new Parser)->parse($response->json('data.token'));
+
+ $this->assertTrue(
+ $token->verify(new Sha256, $server->node->getDecryptedKey()),
+ 'Failed to validate that the JWT data returned was signed using the Node\'s secret key.'
+ );
+
+ // Check that the claims are generated correctly.
+ $this->assertSame(config('app.url'), $token->getClaim('iss'));
+ $this->assertSame($server->node->getConnectionAddress(), $token->getClaim('aud'));
+ $this->assertSame(CarbonImmutable::now()->getTimestamp(), $token->getClaim('iat'));
+ $this->assertSame(CarbonImmutable::now()->subMinutes(5)->getTimestamp(), $token->getClaim('nbf'));
+ $this->assertSame(CarbonImmutable::now()->addMinutes(15)->getTimestamp(), $token->getClaim('exp'));
+ $this->assertSame($user->id, $token->getClaim('user_id'));
+ $this->assertSame($server->uuid, $token->getClaim('server_uuid'));
+ $this->assertSame(['*'], $token->getClaim('permissions'));
+ }
+
+ /**
+ * Test that the subuser's permissions are passed along correctly in the generated JWT.
+ */
+ public function testJwtIsConfiguredCorrectlyForServerSubuser()
+ {
+ $permissions = [Permission::ACTION_WEBSOCKET_CONNECT, Permission::ACTION_CONTROL_CONSOLE];
+
+ /** @var \Pterodactyl\Models\User $user */
+ /** @var \Pterodactyl\Models\Server $server */
+ [$user, $server] = $this->generateTestAccount($permissions);
+
+ $response = $this->actingAs($user)->getJson("/api/client/servers/{$server->uuid}/websocket");
+
+ $response->assertOk();
+ $response->assertJsonStructure(['data' => ['token', 'socket']]);
+
+ $token = (new Parser)->parse($response->json('data.token'));
+
+ $this->assertTrue(
+ $token->verify(new Sha256, $server->node->getDecryptedKey()),
+ 'Failed to validate that the JWT data returned was signed using the Node\'s secret key.'
+ );
+
+ // Check that the claims are generated correctly.
+ $this->assertSame($permissions, $token->getClaim('permissions'));
+ }
+}
diff --git a/tests/Integration/Api/Client/TwoFactorControllerTest.php b/tests/Integration/Api/Client/TwoFactorControllerTest.php
new file mode 100644
index 000000000..8344d2b96
--- /dev/null
+++ b/tests/Integration/Api/Client/TwoFactorControllerTest.php
@@ -0,0 +1,147 @@
+create(['use_totp' => false]);
+
+ $this->assertFalse($user->use_totp);
+ $this->assertEmpty($user->totp_secret);
+ $this->assertEmpty($user->totp_authenticated_at);
+
+ $response = $this->actingAs($user)->getJson('/api/client/account/two-factor');
+
+ $response->assertOk();
+ $response->assertJsonStructure(['data' => ['image_url_data']]);
+
+ $user = $user->refresh();
+
+ $this->assertFalse($user->use_totp);
+ $this->assertNotEmpty($user->totp_secret);
+ $this->assertEmpty($user->totp_authenticated_at);
+ }
+
+ /**
+ * Test that an error is returned if the user's account already has 2FA enabled on it.
+ */
+ public function testErrorIsReturnedWhenTwoFactorIsAlreadyEnabled()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create(['use_totp' => true]);
+
+ $response = $this->actingAs($user)->getJson('/api/client/account/two-factor');
+
+ $response->assertStatus(Response::HTTP_BAD_REQUEST);
+ $response->assertJsonPath('errors.0.code', 'BadRequestHttpException');
+ $response->assertJsonPath('errors.0.detail', 'Two-factor authentication is already enabled on this account.');
+ }
+
+ /**
+ * Test that a validation error is thrown if invalid data is passed to the 2FA endpoint.
+ */
+ public function testValidationErrorIsReturnedIfInvalidDataIsPassedToEnabled2FA()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create(['use_totp' => false]);
+
+ $response = $this->actingAs($user)->postJson('/api/client/account/two-factor', [
+ 'code' => '',
+ ]);
+
+ $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
+ $response->assertJsonPath('errors.0.code', 'required');
+ }
+
+ /**
+ * Tests that 2FA can be enabled on an account for the user.
+ */
+ public function testTwoFactorCanBeEnabledOnAccount()
+ {
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create(['use_totp' => false]);
+
+ // Make the initial call to get the account setup for 2FA.
+ $this->actingAs($user)->getJson('/api/client/account/two-factor')->assertOk();
+
+ $user = $user->refresh();
+ $this->assertNotNull($user->totp_secret);
+
+ /** @var \PragmaRX\Google2FA\Google2FA $service */
+ $service = $this->app->make(Google2FA::class);
+
+ $secret = decrypt($user->totp_secret);
+ $token = $service->getCurrentOtp($secret);
+
+ $response = $this->actingAs($user)->postJson('/api/client/account/two-factor', [
+ 'code' => $token,
+ ]);
+
+ $response->assertStatus(Response::HTTP_NO_CONTENT);
+
+ $user = $user->refresh();
+
+ $this->assertTrue($user->use_totp);
+ }
+
+ /**
+ * Test that two factor authentication can be disabled on an account as long as the password
+ * provided is valid for the account.
+ */
+ public function testTwoFactorCanBeDisabledOnAccount()
+ {
+ Carbon::setTestNow(Carbon::now());
+
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create(['use_totp' => true]);
+
+ $response = $this->actingAs($user)->deleteJson('/api/client/account/two-factor', [
+ 'password' => 'invalid',
+ ]);
+
+ $response->assertStatus(Response::HTTP_BAD_REQUEST);
+ $response->assertJsonPath('errors.0.code', 'BadRequestHttpException');
+ $response->assertJsonPath('errors.0.detail', 'The password provided was not valid.');
+
+ $response = $this->actingAs($user)->deleteJson('/api/client/account/two-factor', [
+ 'password' => 'password',
+ ]);
+
+ $response->assertStatus(Response::HTTP_NO_CONTENT);
+
+ $user = $user->refresh();
+ $this->assertFalse($user->use_totp);
+ $this->assertNotNull($user->totp_authenticated_at);
+ $this->assertSame(Carbon::now()->toIso8601String(), $user->totp_authenticated_at->toIso8601String());
+ }
+
+ /**
+ * Test that no error is returned when trying to disabled two factor on an account where it
+ * was not enabled in the first place.
+ */
+ public function testNoErrorIsReturnedIfTwoFactorIsNotEnabled()
+ {
+ Carbon::setTestNow(Carbon::now());
+
+ /** @var \Pterodactyl\Models\User $user */
+ $user = factory(User::class)->create(['use_totp' => false]);
+
+ $response = $this->actingAs($user)->deleteJson('/api/client/account/two-factor', [
+ 'password' => 'password',
+ ]);
+
+ $response->assertStatus(Response::HTTP_NO_CONTENT);
+ }
+}
diff --git a/tests/Integration/IntegrationTestCase.php b/tests/Integration/IntegrationTestCase.php
index cb81ef6c2..ee7229660 100644
--- a/tests/Integration/IntegrationTestCase.php
+++ b/tests/Integration/IntegrationTestCase.php
@@ -5,10 +5,13 @@ namespace Pterodactyl\Tests\Integration;
use Tests\TestCase;
use Cake\Chronos\Chronos;
use Illuminate\Database\Eloquent\Model;
+use Tests\Traits\Integration\CreatesTestModels;
use Pterodactyl\Transformers\Api\Application\BaseTransformer;
abstract class IntegrationTestCase extends TestCase
{
+ use CreatesTestModels;
+
/**
* Setup base integration test cases.
*/
diff --git a/tests/Traits/Http/IntegrationJsonRequestAssertions.php b/tests/Traits/Http/IntegrationJsonRequestAssertions.php
index 471085db0..c7cce5248 100644
--- a/tests/Traits/Http/IntegrationJsonRequestAssertions.php
+++ b/tests/Traits/Http/IntegrationJsonRequestAssertions.php
@@ -3,14 +3,14 @@
namespace Tests\Traits\Http;
use Illuminate\Http\Response;
-use Illuminate\Foundation\Testing\TestResponse;
+use Illuminate\Testing\TestResponse;
trait IntegrationJsonRequestAssertions
{
/**
* Make assertions about a 404 response on the API.
*
- * @param \Illuminate\Foundation\Testing\TestResponse $response
+ * @param \Illuminate\Testing\TestResponse $response
*/
public function assertNotFoundJson(TestResponse $response)
{
@@ -31,7 +31,7 @@ trait IntegrationJsonRequestAssertions
/**
* Make assertions about a 403 error returned by the API.
*
- * @param \Illuminate\Foundation\Testing\TestResponse $response
+ * @param \Illuminate\Testing\TestResponse $response
*/
public function assertAccessDeniedJson(TestResponse $response)
{
diff --git a/tests/Unit/Commands/Environment/EmailSettingsCommandTest.php b/tests/Unit/Commands/Environment/EmailSettingsCommandTest.php
index 63489c157..d52b7b3fd 100644
--- a/tests/Unit/Commands/Environment/EmailSettingsCommandTest.php
+++ b/tests/Unit/Commands/Environment/EmailSettingsCommandTest.php
@@ -1,11 +1,4 @@
.
- *
- * This software is licensed under the terms of the MIT license.
- * https://opensource.org/licenses/MIT
- */
namespace Tests\Unit\Commands\Environment;
@@ -58,7 +51,7 @@ class EmailSettingsCommandTest extends CommandTestCase
$display = $this->runCommand($this->command, [], array_values($data));
$this->assertNotEmpty($display);
- $this->assertContains('Updating stored environment configuration file.', $display);
+ $this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
@@ -90,7 +83,7 @@ class EmailSettingsCommandTest extends CommandTestCase
]);
$this->assertNotEmpty($display);
- $this->assertContains('Updating stored environment configuration file.', $display);
+ $this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
@@ -115,7 +108,7 @@ class EmailSettingsCommandTest extends CommandTestCase
$display = $this->runCommand($this->command, ['--driver' => 'mail'], array_values($data));
$this->assertNotEmpty($display);
- $this->assertContains('Updating stored environment configuration file.', $display);
+ $this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
@@ -136,7 +129,7 @@ class EmailSettingsCommandTest extends CommandTestCase
$display = $this->runCommand($this->command, [], array_values($data));
$this->assertNotEmpty($display);
- $this->assertContains('Updating stored environment configuration file.', $display);
+ $this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
@@ -164,7 +157,7 @@ class EmailSettingsCommandTest extends CommandTestCase
]);
$this->assertNotEmpty($display);
- $this->assertContains('Updating stored environment configuration file.', $display);
+ $this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
@@ -184,7 +177,7 @@ class EmailSettingsCommandTest extends CommandTestCase
$display = $this->runCommand($this->command, [], array_values($data));
$this->assertNotEmpty($display);
- $this->assertContains('Updating stored environment configuration file.', $display);
+ $this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
@@ -210,7 +203,7 @@ class EmailSettingsCommandTest extends CommandTestCase
]);
$this->assertNotEmpty($display);
- $this->assertContains('Updating stored environment configuration file.', $display);
+ $this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
@@ -235,7 +228,7 @@ class EmailSettingsCommandTest extends CommandTestCase
]);
$this->assertNotEmpty($display);
- $this->assertContains('Updating stored environment configuration file.', $display);
+ $this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
@@ -264,7 +257,7 @@ class EmailSettingsCommandTest extends CommandTestCase
]);
$this->assertNotEmpty($display);
- $this->assertContains('Updating stored environment configuration file.', $display);
+ $this->assertStringContainsString('Updating stored environment configuration file.', $display);
}
/**
diff --git a/tests/Unit/Commands/Location/DeleteLocationCommandTest.php b/tests/Unit/Commands/Location/DeleteLocationCommandTest.php
index 8b292c3f6..26bd73319 100644
--- a/tests/Unit/Commands/Location/DeleteLocationCommandTest.php
+++ b/tests/Unit/Commands/Location/DeleteLocationCommandTest.php
@@ -63,7 +63,7 @@ class DeleteLocationCommandTest extends CommandTestCase
$display = $this->runCommand($this->command, [], [$location2->short]);
$this->assertNotEmpty($display);
- $this->assertContains(trans('command/messages.location.deleted'), $display);
+ $this->assertStringContainsString(trans('command/messages.location.deleted'), $display);
}
/**
@@ -84,7 +84,7 @@ class DeleteLocationCommandTest extends CommandTestCase
]);
$this->assertNotEmpty($display);
- $this->assertContains(trans('command/messages.location.deleted'), $display);
+ $this->assertStringContainsString(trans('command/messages.location.deleted'), $display);
}
/**
@@ -103,8 +103,8 @@ class DeleteLocationCommandTest extends CommandTestCase
$display = $this->runCommand($this->command, [], ['123_not_exist', 'another_not_exist', $location2->short]);
$this->assertNotEmpty($display);
- $this->assertContains(trans('command/messages.location.no_location_found'), $display);
- $this->assertContains(trans('command/messages.location.deleted'), $display);
+ $this->assertStringContainsString(trans('command/messages.location.no_location_found'), $display);
+ $this->assertStringContainsString(trans('command/messages.location.deleted'), $display);
}
/**
@@ -123,6 +123,6 @@ class DeleteLocationCommandTest extends CommandTestCase
$display = $this->withoutInteraction()->runCommand($this->command, ['--short' => 'randomTestString']);
$this->assertNotEmpty($display);
- $this->assertContains(trans('command/messages.location.no_location_found'), $display);
+ $this->assertStringContainsString(trans('command/messages.location.no_location_found'), $display);
}
}
diff --git a/tests/Unit/Commands/Location/MakeLocationCommandTest.php b/tests/Unit/Commands/Location/MakeLocationCommandTest.php
index 48823acfa..6b628a6f0 100644
--- a/tests/Unit/Commands/Location/MakeLocationCommandTest.php
+++ b/tests/Unit/Commands/Location/MakeLocationCommandTest.php
@@ -55,7 +55,7 @@ class MakeLocationCommandTest extends CommandTestCase
$display = $this->runCommand($this->command, [], [$location->short, $location->long]);
$this->assertNotEmpty($display);
- $this->assertContains(trans('command/messages.location.created', [
+ $this->assertStringContainsString(trans('command/messages.location.created', [
'name' => $location->short,
'id' => $location->id,
]), $display);
@@ -79,7 +79,7 @@ class MakeLocationCommandTest extends CommandTestCase
]);
$this->assertNotEmpty($display);
- $this->assertContains(trans('command/messages.location.created', [
+ $this->assertStringContainsString(trans('command/messages.location.created', [
'name' => $location->short,
'id' => $location->id,
]), $display);
diff --git a/tests/Unit/Commands/Maintenance/CleanServiceBackupFilesCommandTest.php b/tests/Unit/Commands/Maintenance/CleanServiceBackupFilesCommandTest.php
index ca3e5b74f..ecb412e52 100644
--- a/tests/Unit/Commands/Maintenance/CleanServiceBackupFilesCommandTest.php
+++ b/tests/Unit/Commands/Maintenance/CleanServiceBackupFilesCommandTest.php
@@ -54,7 +54,7 @@ class CleanServiceBackupFilesCommandTest extends CommandTestCase
$display = $this->runCommand($this->getCommand());
$this->assertNotEmpty($display);
- $this->assertContains(trans('command/messages.maintenance.deleting_service_backup', ['file' => 'testfile.txt']), $display);
+ $this->assertStringContainsString(trans('command/messages.maintenance.deleting_service_backup', ['file' => 'testfile.txt']), $display);
}
/**
diff --git a/tests/Unit/Commands/Schedule/ProcessRunnableCommandTest.php b/tests/Unit/Commands/Schedule/ProcessRunnableCommandTest.php
index d483ede05..5efbef6c4 100644
--- a/tests/Unit/Commands/Schedule/ProcessRunnableCommandTest.php
+++ b/tests/Unit/Commands/Schedule/ProcessRunnableCommandTest.php
@@ -1,11 +1,4 @@
.
- *
- * This software is licensed under the terms of the MIT license.
- * https://opensource.org/licenses/MIT
- */
namespace Tests\Unit\Commands\Schedule;
@@ -64,7 +57,7 @@ class ProcessRunnableCommandTest extends CommandTestCase
$display = $this->runCommand($this->command);
$this->assertNotEmpty($display);
- $this->assertContains(trans('command/messages.schedule.output_line', [
+ $this->assertStringContainsString(trans('command/messages.schedule.output_line', [
'schedule' => $schedule->name,
'hash' => $schedule->hashid,
]), $display);
@@ -83,7 +76,7 @@ class ProcessRunnableCommandTest extends CommandTestCase
$display = $this->runCommand($this->command);
$this->assertNotEmpty($display);
- $this->assertNotContains(trans('command/messages.schedule.output_line', [
+ $this->assertStringNotContainsString(trans('command/messages.schedule.output_line', [
'schedule' => $schedule->name,
'hash' => $schedule->hashid,
]), $display);
@@ -101,7 +94,7 @@ class ProcessRunnableCommandTest extends CommandTestCase
$display = $this->runCommand($this->command);
$this->assertNotEmpty($display);
- $this->assertNotContains(trans('command/messages.schedule.output_line', [
+ $this->assertStringNotContainsString(trans('command/messages.schedule.output_line', [
'schedule' => $schedule->name,
'hash' => $schedule->hashid,
]), $display);
diff --git a/tests/Unit/Commands/Server/BulkPowerActionCommandTest.php b/tests/Unit/Commands/Server/BulkPowerActionCommandTest.php
index 7bac15d70..d1ba90cf4 100644
--- a/tests/Unit/Commands/Server/BulkPowerActionCommandTest.php
+++ b/tests/Unit/Commands/Server/BulkPowerActionCommandTest.php
@@ -6,21 +6,23 @@ use Mockery as m;
use Pterodactyl\Models\Node;
use GuzzleHttp\Psr7\Response;
use Pterodactyl\Models\Server;
+use Illuminate\Support\Collection;
use Illuminate\Validation\Factory;
use Tests\Unit\Commands\CommandTestCase;
+use Illuminate\Validation\ValidationException;
+use Pterodactyl\Repositories\Wings\DaemonPowerRepository;
use Pterodactyl\Console\Commands\Server\BulkPowerActionCommand;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
-use Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface;
class BulkPowerActionCommandTest extends CommandTestCase
{
/**
- * @var \Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
private $powerRepository;
/**
- * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
private $repository;
@@ -31,7 +33,7 @@ class BulkPowerActionCommandTest extends CommandTestCase
{
parent::setUp();
- $this->powerRepository = m::mock(PowerRepositoryInterface::class);
+ $this->powerRepository = m::mock(DaemonPowerRepository::class);
$this->repository = m::mock(ServerRepositoryInterface::class);
}
@@ -47,28 +49,18 @@ class BulkPowerActionCommandTest extends CommandTestCase
$server->setRelation('node', factory(Node::class)->make());
}
- $this->repository->shouldReceive('getServersForPowerActionCount')
- ->once()
- ->with([], [])
- ->andReturn(2);
-
- $this->repository->shouldReceive('getServersForPowerAction')
- ->once()
- ->with([], [])
- ->andReturn($servers);
+ $this->repository->expects('getServersForPowerActionCount')->with([], [])->andReturn(2);
+ $this->repository->expects('getServersForPowerAction')->with([], [])->andReturn($servers);
for ($i = 0; $i < count($servers); $i++) {
- $this->powerRepository->shouldReceive('setNode->setServer->sendSignal')
- ->once()
- ->with('kill')
- ->andReturnNull();
+ $this->powerRepository->expects('setNode->setServer->send')->with('kill')->andReturnNull();
}
$display = $this->runCommand($this->getCommand(), ['action' => 'kill'], ['yes']);
$this->assertNotEmpty($display);
- $this->assertContains('2/2', $display);
- $this->assertContains(trans('command/messages.server.power.confirm', ['action' => 'kill', 'count' => 2]), $display);
+ $this->assertStringContainsString('2/2', $display);
+ $this->assertStringContainsString(trans('command/messages.server.power.confirm', ['action' => 'kill', 'count' => 2]), $display);
}
/**
@@ -79,19 +71,17 @@ class BulkPowerActionCommandTest extends CommandTestCase
$server = factory(Server::class)->make();
$server->setRelation('node', $node = factory(Node::class)->make());
- $this->repository->shouldReceive('getServersForPowerActionCount')
- ->once()
+ $this->repository->expects('getServersForPowerActionCount')
->with([1, 2], [3, 4])
->andReturn(1);
- $this->repository->shouldReceive('getServersForPowerAction')
- ->once()
+ $this->repository->expects('getServersForPowerAction')
->with([1, 2], [3, 4])
- ->andReturn([$server]);
+ ->andReturn(Collection::make([$server]));
$this->powerRepository->expects('setNode')->with($node)->andReturnSelf();
$this->powerRepository->expects('setServer')->with($server)->andReturnSelf();
- $this->powerRepository->expects('sendSignal')->with('kill')->andReturn(new Response);
+ $this->powerRepository->expects('send')->with('kill')->andReturn(new Response);
$display = $this->runCommand($this->getCommand(), [
'action' => 'kill',
@@ -100,8 +90,8 @@ class BulkPowerActionCommandTest extends CommandTestCase
], ['yes']);
$this->assertNotEmpty($display);
- $this->assertContains('1/1', $display);
- $this->assertContains(trans('command/messages.server.power.confirm', ['action' => 'kill', 'count' => 1]), $display);
+ $this->assertStringContainsString('1/1', $display);
+ $this->assertStringContainsString(trans('command/messages.server.power.confirm', ['action' => 'kill', 'count' => 1]), $display);
}
/**
@@ -112,13 +102,12 @@ class BulkPowerActionCommandTest extends CommandTestCase
$server = factory(Server::class)->make();
$server->setRelation('node', factory(Node::class)->make());
- $this->repository->shouldReceive('getServersForPowerActionCount')
- ->once()
+ $this->repository->expects('getServersForPowerActionCount')
->with([], [])
->andReturn(1);
- $this->repository->shouldReceive('getServersForPowerAction')->once()->with([], [])->andReturn([$server]);
- $this->powerRepository->shouldReceive('setNode->setServer->sendSignal')->once()->with('kill')->andReturnNull();
+ $this->repository->expects('getServersForPowerAction')->with([], [])->andReturn(Collection::make([$server]));
+ $this->powerRepository->expects('setNode->setServer->send')->with('kill')->andReturnNull();
$display = $this->runCommand($this->getCommand(), [
'action' => 'kill',
@@ -127,8 +116,8 @@ class BulkPowerActionCommandTest extends CommandTestCase
], ['yes']);
$this->assertNotEmpty($display);
- $this->assertContains('1/1', $display);
- $this->assertContains(trans('command/messages.server.power.confirm', ['action' => 'kill', 'count' => 1]), $display);
+ $this->assertStringContainsString('1/1', $display);
+ $this->assertStringContainsString(trans('command/messages.server.power.confirm', ['action' => 'kill', 'count' => 1]), $display);
}
/**
@@ -137,10 +126,10 @@ class BulkPowerActionCommandTest extends CommandTestCase
* @param array $data
*
* @dataProvider validationFailureDataProvider
- * @expectedException \Illuminate\Validation\ValidationException
*/
public function testValidationErrors(array $data)
{
+ $this->expectException(ValidationException::class);
$this->runCommand($this->getCommand(), $data);
}
diff --git a/tests/Unit/Commands/User/DeleteUserCommandTest.php b/tests/Unit/Commands/User/DeleteUserCommandTest.php
index 77516bbf7..8263c2241 100644
--- a/tests/Unit/Commands/User/DeleteUserCommandTest.php
+++ b/tests/Unit/Commands/User/DeleteUserCommandTest.php
@@ -63,7 +63,7 @@ class DeleteUserCommandTest extends CommandTestCase
$this->assertTableContains($user1->id, $display);
$this->assertTableContains($user1->email, $display);
$this->assertTableContains($user1->name, $display);
- $this->assertContains(trans('command/messages.user.deleted'), $display);
+ $this->assertStringContainsString(trans('command/messages.user.deleted'), $display);
}
/**
@@ -84,11 +84,11 @@ class DeleteUserCommandTest extends CommandTestCase
$display = $this->runCommand($this->command, [], ['noResults', $user1->username, $user1->id, 'yes']);
$this->assertNotEmpty($display);
- $this->assertContains(trans('command/messages.user.no_users_found'), $display);
+ $this->assertStringContainsString(trans('command/messages.user.no_users_found'), $display);
$this->assertTableContains($user1->id, $display);
$this->assertTableContains($user1->email, $display);
$this->assertTableContains($user1->name, $display);
- $this->assertContains(trans('command/messages.user.deleted'), $display);
+ $this->assertStringContainsString(trans('command/messages.user.deleted'), $display);
}
/**
@@ -107,11 +107,11 @@ class DeleteUserCommandTest extends CommandTestCase
$display = $this->runCommand($this->command, [], [$user1->username, 0, $user1->username, $user1->id, 'yes']);
$this->assertNotEmpty($display);
- $this->assertContains(trans('command/messages.user.select_search_user'), $display);
+ $this->assertStringContainsString(trans('command/messages.user.select_search_user'), $display);
$this->assertTableContains($user1->id, $display);
$this->assertTableContains($user1->email, $display);
$this->assertTableContains($user1->name, $display);
- $this->assertContains(trans('command/messages.user.deleted'), $display);
+ $this->assertStringContainsString(trans('command/messages.user.deleted'), $display);
}
/**
@@ -130,7 +130,7 @@ class DeleteUserCommandTest extends CommandTestCase
$display = $this->runCommand($this->command, [], [$user1->username, $user1->id, 'no']);
$this->assertNotEmpty($display);
- $this->assertNotContains(trans('command/messages.user.deleted'), $display);
+ $this->assertStringNotContainsString(trans('command/messages.user.deleted'), $display);
}
/**
@@ -149,7 +149,7 @@ class DeleteUserCommandTest extends CommandTestCase
$display = $this->withoutInteraction()->runCommand($this->command, ['--user' => $user1->username]);
$this->assertNotEmpty($display);
- $this->assertContains(trans('command/messages.user.deleted'), $display);
+ $this->assertStringContainsString(trans('command/messages.user.deleted'), $display);
}
/**
@@ -169,7 +169,7 @@ class DeleteUserCommandTest extends CommandTestCase
$display = $this->withoutInteraction()->runCommand($this->command, ['--user' => $user1->username]);
$this->assertNotEmpty($display);
- $this->assertContains(trans('command/messages.user.multiple_found'), $display);
+ $this->assertStringContainsString(trans('command/messages.user.multiple_found'), $display);
}
/**
@@ -183,6 +183,6 @@ class DeleteUserCommandTest extends CommandTestCase
$display = $this->withoutInteraction()->runCommand($this->command, ['--user' => 123456]);
$this->assertNotEmpty($display);
- $this->assertContains(trans('command/messages.user.no_users_found'), $display);
+ $this->assertStringContainsString(trans('command/messages.user.no_users_found'), $display);
}
}
diff --git a/tests/Unit/Commands/User/DisableTwoFactorCommandTest.php b/tests/Unit/Commands/User/DisableTwoFactorCommandTest.php
index 28c9377ff..f2868883d 100644
--- a/tests/Unit/Commands/User/DisableTwoFactorCommandTest.php
+++ b/tests/Unit/Commands/User/DisableTwoFactorCommandTest.php
@@ -58,7 +58,7 @@ class DisableTwoFactorCommandTest extends CommandTestCase
$display = $this->runCommand($this->command, [], [$user->email]);
$this->assertNotEmpty($display);
- $this->assertContains(trans('command/messages.user.2fa_disabled', ['email' => $user->email]), $display);
+ $this->assertStringContainsString(trans('command/messages.user.2fa_disabled', ['email' => $user->email]), $display);
}
/**
@@ -78,6 +78,6 @@ class DisableTwoFactorCommandTest extends CommandTestCase
$display = $this->withoutInteraction()->runCommand($this->command, ['--email' => $user->email]);
$this->assertNotEmpty($display);
- $this->assertContains(trans('command/messages.user.2fa_disabled', ['email' => $user->email]), $display);
+ $this->assertStringContainsString(trans('command/messages.user.2fa_disabled', ['email' => $user->email]), $display);
}
}
diff --git a/tests/Unit/Commands/User/MakeUserCommandTest.php b/tests/Unit/Commands/User/MakeUserCommandTest.php
index d67e90ab5..89baa3323 100644
--- a/tests/Unit/Commands/User/MakeUserCommandTest.php
+++ b/tests/Unit/Commands/User/MakeUserCommandTest.php
@@ -61,12 +61,12 @@ class MakeUserCommandTest extends CommandTestCase
]);
$this->assertNotEmpty($display);
- $this->assertContains(trans('command/messages.user.ask_password_help'), $display);
- $this->assertContains($user->uuid, $display);
- $this->assertContains($user->email, $display);
- $this->assertContains($user->username, $display);
- $this->assertContains($user->name, $display);
- $this->assertContains('Yes', $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);
}
/**
@@ -90,7 +90,7 @@ class MakeUserCommandTest extends CommandTestCase
]);
$this->assertNotEmpty($display);
- $this->assertNotContains(trans('command/messages.user.ask_password_help'), $display);
+ $this->assertStringNotContainsString(trans('command/messages.user.ask_password_help'), $display);
}
/**
@@ -119,11 +119,11 @@ class MakeUserCommandTest extends CommandTestCase
]);
$this->assertNotEmpty($display);
- $this->assertNotContains(trans('command/messages.user.ask_password_help'), $display);
- $this->assertContains($user->uuid, $display);
- $this->assertContains($user->email, $display);
- $this->assertContains($user->username, $display);
- $this->assertContains($user->name, $display);
- $this->assertContains('No', $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);
}
}
diff --git a/tests/Unit/Http/Controllers/Admin/StatisticsControllerTest.php b/tests/Unit/Http/Controllers/Admin/StatisticsControllerTest.php
deleted file mode 100644
index d9ea3ec28..000000000
--- a/tests/Unit/Http/Controllers/Admin/StatisticsControllerTest.php
+++ /dev/null
@@ -1,110 +0,0 @@
-allocationRepository = m::mock(AllocationRepositoryInterface::class);
- $this->databaseRepository = m::mock(DatabaseRepositoryInterface::class);
- $this->eggRepository = m::mock(EggRepositoryInterface::class);
- $this->nodeRepository = m::mock(NodeRepositoryInterface::class);
- $this->serverRepository = m::mock(ServerRepositoryInterface::class);
- $this->userRepository = m::mock(UserRepositoryInterface::class);
- }
-
- public function testIndexController()
- {
- $controller = $this->getController();
-
- $this->serverRepository->shouldReceive('all')->withNoArgs();
- $this->nodeRepository->shouldReceive('all')->withNoArgs()->andReturn(collect([factory(Node::class)->make(), factory(Node::class)->make()]));
- $this->userRepository->shouldReceive('count')->withNoArgs();
- $this->eggRepository->shouldReceive('count')->withNoArgs();
- $this->databaseRepository->shouldReceive('count')->withNoArgs();
- $this->allocationRepository->shouldReceive('count')->withNoArgs();
- $this->serverRepository->shouldReceive('getSuspendedServersCount')->withNoArgs();
-
- $this->nodeRepository->shouldReceive('getUsageStatsRaw')->twice()->andReturn([
- 'memory' => [
- 'value' => 1024,
- 'max' => 512,
- ],
- 'disk' => [
- 'value' => 1024,
- 'max' => 512,
- ],
- ]);
-
- $controller->shouldReceive('injectJavascript')->once();
-
- $response = $controller->index();
-
- $this->assertIsViewResponse($response);
- $this->assertViewNameEquals('admin.statistics', $response);
- }
-
- private function getController()
- {
- return $this->buildMockedController(StatisticsController::class, [$this->allocationRepository,
- $this->databaseRepository,
- $this->eggRepository,
- $this->nodeRepository,
- $this->serverRepository,
- $this->userRepository, ]
- );
- }
-}
diff --git a/tests/Unit/Http/Controllers/Base/AccountControllerTest.php b/tests/Unit/Http/Controllers/Base/AccountControllerTest.php
deleted file mode 100644
index e69de29bb..000000000
diff --git a/tests/Unit/Http/Controllers/Base/IndexControllerTest.php b/tests/Unit/Http/Controllers/Base/IndexControllerTest.php
deleted file mode 100644
index 7be401ac2..000000000
--- a/tests/Unit/Http/Controllers/Base/IndexControllerTest.php
+++ /dev/null
@@ -1,181 +0,0 @@
-.
- *
- * This software is licensed under the terms of the MIT license.
- * https://opensource.org/licenses/MIT
- */
-
-namespace Tests\Unit\Http\Controllers\Base;
-
-use Mockery as m;
-use Pterodactyl\Models\User;
-use GuzzleHttp\Psr7\Response;
-use Pterodactyl\Models\Server;
-use GuzzleHttp\Psr7\ServerRequest;
-use GuzzleHttp\Exception\ConnectException;
-use GuzzleHttp\Exception\RequestException;
-use Tests\Assertions\ControllerAssertionsTrait;
-use Tests\Unit\Http\Controllers\ControllerTestCase;
-use Pterodactyl\Http\Controllers\Base\IndexController;
-use Illuminate\Contracts\Pagination\LengthAwarePaginator;
-use Symfony\Component\HttpKernel\Exception\HttpException;
-use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService;
-use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
-use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
-
-class IndexControllerTest extends ControllerTestCase
-{
- use ControllerAssertionsTrait;
-
- /**
- * @var \Pterodactyl\Http\Controllers\Base\IndexController
- */
- protected $controller;
-
- /**
- * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface|\Mockery\Mock
- */
- protected $daemonRepository;
-
- /**
- * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService|\Mockery\Mock
- */
- protected $keyProviderService;
-
- /**
- * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock
- */
- protected $repository;
-
- /**
- * Setup tests.
- */
- public function setUp(): void
- {
- parent::setUp();
-
- $this->daemonRepository = m::mock(DaemonServerRepositoryInterface::class);
- $this->keyProviderService = m::mock(DaemonKeyProviderService::class);
- $this->repository = m::mock(ServerRepositoryInterface::class);
-
- $this->controller = new IndexController($this->keyProviderService, $this->daemonRepository, $this->repository);
- }
-
- /**
- * Test the index controller.
- */
- public function testIndexController()
- {
- $paginator = m::mock(LengthAwarePaginator::class);
- $model = $this->generateRequestUserModel();
-
- $this->request->shouldReceive('input')->with('query')->once()->andReturn('searchTerm');
- $this->repository->shouldReceive('setSearchTerm')->with('searchTerm')->once()->andReturnSelf()
- ->shouldReceive('filterUserAccessServers')->with($model, User::FILTER_LEVEL_ALL, config('pterodactyl.paginate.frontend.servers'))
- ->once()->andReturn($paginator);
-
- $response = $this->controller->index($this->request);
- $this->assertIsViewResponse($response);
- $this->assertViewNameEquals('templates.base.core', $response);
- $this->assertViewHasKey('servers', $response);
- $this->assertViewKeyEquals('servers', $paginator, $response);
- }
-
- /**
- * Test the status controller.
- */
- public function testStatusController()
- {
- $user = $this->generateRequestUserModel();
- $server = factory(Server::class)->make(['suspended' => 0, 'installed' => 1]);
- $psrResponse = new Response;
-
- $this->repository->shouldReceive('findFirstWhere')->with([['uuidShort', '=', $server->uuidShort]])->once()->andReturn($server);
- $this->keyProviderService->shouldReceive('handle')->with($server, $user)->once()->andReturn('test123');
-
- $this->daemonRepository->shouldReceive('setServer')->with($server)->once()->andReturnSelf()
- ->shouldReceive('setToken')->with('test123')->once()->andReturnSelf()
- ->shouldReceive('details')->withNoArgs()->once()->andReturn($psrResponse);
-
- $response = $this->controller->status($this->request, $server->uuidShort);
- $this->assertIsJsonResponse($response);
- $this->assertResponseJsonEquals(json_encode($psrResponse->getBody()), $response);
- }
-
- /**
- * Test the status controller if a server is not installed.
- */
- public function testStatusControllerWhenServerNotInstalled()
- {
- $user = $this->generateRequestUserModel();
- $server = factory(Server::class)->make(['suspended' => 0, 'installed' => 0]);
-
- $this->repository->shouldReceive('findFirstWhere')->with([['uuidShort', '=', $server->uuidShort]])->once()->andReturn($server);
- $this->keyProviderService->shouldReceive('handle')->with($server, $user)->once()->andReturn('test123');
-
- $response = $this->controller->status($this->request, $server->uuidShort);
- $this->assertIsJsonResponse($response);
- $this->assertResponseCodeEquals(200, $response);
- $this->assertResponseJsonEquals(['status' => 20], $response);
- }
-
- /**
- * Test the status controller when a server is suspended.
- */
- public function testStatusControllerWhenServerIsSuspended()
- {
- $user = factory(User::class)->make();
- $server = factory(Server::class)->make(['suspended' => 1, 'installed' => 1]);
-
- $this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($user);
- $this->repository->shouldReceive('findFirstWhere')->with([['uuidShort', '=', $server->uuidShort]])->once()->andReturn($server);
- $this->keyProviderService->shouldReceive('handle')->with($server, $user)->once()->andReturn('test123');
-
- $response = $this->controller->status($this->request, $server->uuidShort);
- $this->assertIsJsonResponse($response);
- $this->assertResponseCodeEquals(200, $response);
- $this->assertResponseJsonEquals(['status' => 30], $response);
- }
-
- /**
- * Test the status controller with a ServerConnectionException.
- */
- public function testStatusControllerWithServerConnectionException()
- {
- $user = factory(User::class)->make();
- $server = factory(Server::class)->make(['suspended' => 0, 'installed' => 1]);
-
- $this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($user);
- $this->repository->shouldReceive('findFirstWhere')->with([['uuidShort', '=', $server->uuidShort]])->once()->andReturn($server);
- $this->keyProviderService->shouldReceive('handle')->with($server, $user)->once()->andReturn('test123');
-
- $this->daemonRepository->shouldReceive('setServer')->with($server)->once()->andReturnSelf()
- ->shouldReceive('setToken')->with('test123')->once()->andReturnSelf()
- ->shouldReceive('details')->withNoArgs()->once()->andThrow(new ConnectException('bad connection', new ServerRequest('', '')));
-
- $this->expectExceptionObject(new HttpException(500, 'bad connection'));
- $this->controller->status($this->request, $server->uuidShort);
- }
-
- /**
- * Test the status controller with a RequestException.
- */
- public function testStatusControllerWithRequestException()
- {
- $user = factory(User::class)->make();
- $server = factory(Server::class)->make(['suspended' => 0, 'installed' => 1]);
-
- $this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($user);
- $this->repository->shouldReceive('findFirstWhere')->with([['uuidShort', '=', $server->uuidShort]])->once()->andReturn($server);
- $this->keyProviderService->shouldReceive('handle')->with($server, $user)->once()->andReturn('test123');
-
- $this->daemonRepository->shouldReceive('setServer')->with($server)->once()->andReturnSelf()
- ->shouldReceive('setToken')->with('test123')->once()->andReturnSelf()
- ->shouldReceive('details')->withNoArgs()->once()->andThrow(new RequestException('bad request', new ServerRequest('', '')));
-
- $this->expectExceptionObject(new HttpException(500, 'bad request'));
- $this->controller->status($this->request, $server->uuidShort);
- }
-}
diff --git a/tests/Unit/Http/Controllers/Base/SecurityControllerTest.php b/tests/Unit/Http/Controllers/Base/SecurityControllerTest.php
deleted file mode 100644
index fb79d3b3e..000000000
--- a/tests/Unit/Http/Controllers/Base/SecurityControllerTest.php
+++ /dev/null
@@ -1,156 +0,0 @@
-alert = m::mock(AlertsMessageBag::class);
- $this->config = m::mock(Repository::class);
- $this->repository = m::mock(SessionRepositoryInterface::class);
- $this->toggleTwoFactorService = m::mock(ToggleTwoFactorService::class);
- $this->twoFactorSetupService = m::mock(TwoFactorSetupService::class);
- }
-
- /**
- * Test TOTP generation controller.
- */
- public function testIndexWithout2FactorEnabled()
- {
- $model = $this->generateRequestUserModel(['use_totp' => 0]);
-
- $this->twoFactorSetupService->shouldReceive('handle')->with($model)->once()->andReturn(new Collection([
- 'image' => 'test-image',
- 'secret' => 'secret-code',
- ]));
-
- $response = $this->getController()->index($this->request);
- $this->assertIsJsonResponse($response);
- $this->assertResponseCodeEquals(Response::HTTP_OK, $response);
- $this->assertResponseJsonEquals(['enabled' => false, 'qr_image' => 'test-image', 'secret' => 'secret-code'], $response);
- $this->assertResponseJsonEquals(['qrImage' => 'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=qrCodeImage'], $response);
- }
-
- /**
- * Test TOTP setting controller when no exception is thrown by the service.
- */
- public function testIndexWith2FactorEnabled()
- {
- $this->generateRequestUserModel(['use_totp' => 1]);
-
- $response = $this->getController()->index($this->request);
- $this->assertIsJsonResponse($response);
- $this->assertResponseCodeEquals(Response::HTTP_OK, $response);
- $this->assertResponseJsonEquals(['enabled' => true], $response);
- }
-
- /**
- * Test that a 2FA token can be stored or deleted.
- *
- * @param string $func
- * @dataProvider functionCallDataProvider
- */
- public function testStore(string $func)
- {
- $model = $this->generateRequestUserModel();
-
- $this->mockRequestInput('token', 'some-token');
-
- if ($func === 'delete') {
- $this->toggleTwoFactorService->shouldReceive('handle')->with($model, 'some-token', false);
- } else {
- $this->toggleTwoFactorService->shouldReceive('handle')->with($model, 'some-token');
- }
-
- $response = $this->getController()->{$func}($this->request);
- $this->assertIsJsonResponse($response);
- $this->assertResponseCodeEquals(Response::HTTP_OK, $response);
- $this->assertResponseJsonEquals(['success' => true], $response);
- }
-
- /**
- * Test an invalid token exception is handled.
- *
- * @param string $func
- * @dataProvider functionCallDataProvider
- */
- public function testStoreWithInvalidTokenException(string $func)
- {
- $this->generateRequestUserModel();
-
- $this->mockRequestInput('token');
- $this->toggleTwoFactorService->shouldReceive('handle')->andThrow(new TwoFactorAuthenticationTokenInvalid);
-
- $response = $this->getController()->{$func}($this->request);
- $this->assertIsJsonResponse($response);
- $this->assertResponseCodeEquals(Response::HTTP_OK, $response);
- $this->assertResponseJsonEquals(['success' => false], $response);
- }
-
- /**
- * @return array
- */
- public function functionCallDataProvider()
- {
- return [['store'], ['delete']];
- }
-
- /**
- * Return an instance of the controller for testing with mocked dependencies.
- *
- * @return \Pterodactyl\Http\Controllers\Base\SecurityController
- */
- private function getController(): SecurityController
- {
- return new SecurityController(
- $this->alert,
- $this->config,
- $this->repository,
- $this->toggleTwoFactorService,
- $this->twoFactorSetupService
- );
- }
-}
diff --git a/tests/Unit/Http/Middleware/AdminAuthenticateTest.php b/tests/Unit/Http/Middleware/AdminAuthenticateTest.php
index eee9a6969..efe6e8212 100644
--- a/tests/Unit/Http/Middleware/AdminAuthenticateTest.php
+++ b/tests/Unit/Http/Middleware/AdminAuthenticateTest.php
@@ -4,6 +4,7 @@ namespace Tests\Unit\Http\Middleware;
use Pterodactyl\Models\User;
use Pterodactyl\Http\Middleware\AdminAuthenticate;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class AdminAuthenticateTest extends MiddlewareTestCase
{
@@ -21,11 +22,11 @@ class AdminAuthenticateTest extends MiddlewareTestCase
/**
* Test that a missing user in the request triggers an error.
- *
- * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
public function testExceptionIsThrownIfUserDoesNotExist()
{
+ $this->expectException(AccessDeniedHttpException::class);
+
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull();
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
@@ -33,11 +34,11 @@ class AdminAuthenticateTest extends MiddlewareTestCase
/**
* Test that an exception is thrown if the user is not an admin.
- *
- * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
public function testExceptionIsThrownIfUserIsNotAnAdmin()
{
+ $this->expectException(AccessDeniedHttpException::class);
+
$user = factory(User::class)->make(['root_admin' => 0]);
$this->request->shouldReceive('user')->withNoArgs()->twice()->andReturn($user);
diff --git a/tests/Unit/Http/Middleware/Api/Application/AuthenticateUserTest.php b/tests/Unit/Http/Middleware/Api/Application/AuthenticateUserTest.php
index 7c0cfc9e7..3cbd7debf 100644
--- a/tests/Unit/Http/Middleware/Api/Application/AuthenticateUserTest.php
+++ b/tests/Unit/Http/Middleware/Api/Application/AuthenticateUserTest.php
@@ -1,19 +1,20 @@
expectException(AccessDeniedHttpException::class);
+
$this->setRequestUserModel(null);
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
@@ -21,11 +22,11 @@ class AuthenticateUserTest extends MiddlewareTestCase
/**
* Test that a non-admin user results an an exception.
- *
- * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
public function testNonAdminUser()
{
+ $this->expectException(AccessDeniedHttpException::class);
+
$this->generateRequestUserModel(['root_admin' => false]);
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
diff --git a/tests/Unit/Http/Middleware/API/AuthenticateIPAccessTest.php b/tests/Unit/Http/Middleware/Api/AuthenticateIPAccessTest.php
similarity index 92%
rename from tests/Unit/Http/Middleware/API/AuthenticateIPAccessTest.php
rename to tests/Unit/Http/Middleware/Api/AuthenticateIPAccessTest.php
index 967fae0a3..8d47fdd54 100644
--- a/tests/Unit/Http/Middleware/API/AuthenticateIPAccessTest.php
+++ b/tests/Unit/Http/Middleware/Api/AuthenticateIPAccessTest.php
@@ -1,10 +1,11 @@
expectException(AccessDeniedHttpException::class);
+
$model = factory(ApiKey::class)->make(['allowed_ips' => '["127.0.0.1"]']);
$this->setRequestAttribute('api_key', $model);
diff --git a/tests/Unit/Http/Middleware/API/AuthenticateKeyTest.php b/tests/Unit/Http/Middleware/Api/AuthenticateKeyTest.php
similarity index 96%
rename from tests/Unit/Http/Middleware/API/AuthenticateKeyTest.php
rename to tests/Unit/Http/Middleware/Api/AuthenticateKeyTest.php
index 2b1b91a90..79715e4c8 100644
--- a/tests/Unit/Http/Middleware/API/AuthenticateKeyTest.php
+++ b/tests/Unit/Http/Middleware/Api/AuthenticateKeyTest.php
@@ -1,6 +1,6 @@
expectException(AccessDeniedHttpException::class);
+
$this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn('abcd1234');
$this->repository->shouldReceive('findFirstWhere')->andThrow(new RecordNotFoundException);
@@ -141,11 +142,11 @@ class AuthenticateKeyTest extends MiddlewareTestCase
/**
* Test that a valid token identifier with an invalid token attached to it
* triggers an exception.
- *
- * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
public function testInvalidTokenForIdentifier()
{
+ $this->expectException(AccessDeniedHttpException::class);
+
$model = factory(ApiKey::class)->make();
$this->request->shouldReceive('bearerToken')->withNoArgs()->twice()->andReturn($model->identifier . 'asdf');
diff --git a/tests/Unit/Http/Middleware/Api/Daemon/DaemonAuthenticateTest.php b/tests/Unit/Http/Middleware/Api/Daemon/DaemonAuthenticateTest.php
index f5de32679..35699eb65 100644
--- a/tests/Unit/Http/Middleware/Api/Daemon/DaemonAuthenticateTest.php
+++ b/tests/Unit/Http/Middleware/Api/Daemon/DaemonAuthenticateTest.php
@@ -4,19 +4,27 @@ namespace Tests\Unit\Http\Middleware\Api\Daemon;
use Mockery as m;
use Pterodactyl\Models\Node;
+use Illuminate\Contracts\Encryption\Encrypter;
use Tests\Unit\Http\Middleware\MiddlewareTestCase;
+use Pterodactyl\Repositories\Eloquent\NodeRepository;
use Symfony\Component\HttpKernel\Exception\HttpException;
-use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class DaemonAuthenticateTest extends MiddlewareTestCase
{
/**
- * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
private $repository;
+ /**
+ * @var \Mockery\MockInterface
+ */
+ private $encrypter;
+
/**
* Setup tests.
*/
@@ -24,7 +32,8 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
{
parent::setUp();
- $this->repository = m::mock(NodeRepositoryInterface::class);
+ $this->encrypter = m::mock(Encrypter::class);
+ $this->repository = m::mock(NodeRepository::class);
}
/**
@@ -33,7 +42,7 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
*/
public function testResponseShouldContinueIfRouteIsExempted()
{
- $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('daemon.configuration');
+ $this->request->expects('route->getName')->withNoArgs()->andReturn('daemon.configuration');
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
}
@@ -44,8 +53,8 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
*/
public function testResponseShouldFailIfNoTokenIsProvided()
{
- $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.route');
- $this->request->shouldReceive('bearerToken')->withNoArgs()->once()->andReturnNull();
+ $this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
+ $this->request->expects('bearerToken')->withNoArgs()->andReturnNull();
try {
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
@@ -58,17 +67,54 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
}
/**
- * Test that passing in an invalid node daemon secret will result in a HTTP/403
- * error response.
+ * Test that passing in an invalid node daemon secret will result in a bad request
+ * exception being returned.
*
- * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+ * @param string $token
+ * @dataProvider badTokenDataProvider
*/
- public function testResponseShouldFailIfNoNodeIsFound()
+ public function testResponseShouldFailIfTokenFormatIsIncorrect(string $token)
{
- $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.route');
- $this->request->shouldReceive('bearerToken')->withNoArgs()->once()->andReturn('test1234');
+ $this->expectException(BadRequestHttpException::class);
- $this->repository->shouldReceive('findFirstWhere')->with([['daemonSecret', '=', 'test1234']])->once()->andThrow(new RecordNotFoundException);
+ $this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
+ $this->request->expects('bearerToken')->withNoArgs()->andReturn($token);
+
+ $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
+ }
+
+ /**
+ * Test that an access denied error is returned if the node is valid but the token
+ * provided is not valid.
+ */
+ public function testResponseShouldFailIfTokenIsNotValid()
+ {
+ $this->expectException(AccessDeniedHttpException::class);
+
+ /** @var \Pterodactyl\Models\Node $model */
+ $model = factory(Node::class)->make();
+
+ $this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
+ $this->request->expects('bearerToken')->withNoArgs()->andReturn($model->daemon_token_id . '.random_string_123');
+
+ $this->repository->expects('findFirstWhere')->with(['daemon_token_id' => $model->daemon_token_id])->andReturn($model);
+ $this->encrypter->expects('decrypt')->with($model->daemon_token)->andReturns(decrypt($model->daemon_token));
+
+ $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
+ }
+
+ /**
+ * Test that an access denied exception is returned if the node is not found using
+ * the token ID provided.
+ */
+ public function testResponseShouldFailIfNodeIsNotFound()
+ {
+ $this->expectException(AccessDeniedHttpException::class);
+
+ $this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
+ $this->request->expects('bearerToken')->withNoArgs()->andReturn('abcd1234.random_string_123');
+
+ $this->repository->expects('findFirstWhere')->with(['daemon_token_id' => 'abcd1234'])->andThrow(RecordNotFoundException::class);
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
}
@@ -78,18 +124,39 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
*/
public function testSuccessfulMiddlewareProcess()
{
+ /** @var \Pterodactyl\Models\Node $model */
$model = factory(Node::class)->make();
- $this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.route');
- $this->request->shouldReceive('bearerToken')->withNoArgs()->once()->andReturn($model->daemonSecret);
+ $this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
+ $this->request->expects('bearerToken')->withNoArgs()->andReturn($model->daemon_token_id . '.' . decrypt($model->daemon_token));
- $this->repository->shouldReceive('findFirstWhere')->with([['daemonSecret', '=', $model->daemonSecret]])->once()->andReturn($model);
+ $this->repository->expects('findFirstWhere')->with(['daemon_token_id' => $model->daemon_token_id])->andReturn($model);
+ $this->encrypter->expects('decrypt')->with($model->daemon_token)->andReturns(decrypt($model->daemon_token));
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
$this->assertRequestHasAttribute('node');
$this->assertRequestAttributeEquals($model, 'node');
}
+ /**
+ * Provides different tokens that should trigger a bad request exception due to
+ * their formatting.
+ *
+ * @return array|\string[][]
+ */
+ public function badTokenDataProvider(): array
+ {
+ return [
+ ['foo'],
+ ['foobar'],
+ ['foo-bar'],
+ ['foo.bar.baz'],
+ ['.foo'],
+ ['foo.'],
+ ['foo..bar'],
+ ];
+ }
+
/**
* Return an instance of the middleware using mocked dependencies.
*
@@ -97,6 +164,6 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
*/
private function getMiddleware(): DaemonAuthenticate
{
- return new DaemonAuthenticate($this->repository);
+ return new DaemonAuthenticate($this->encrypter, $this->repository);
}
}
diff --git a/tests/Unit/Http/Middleware/API/SetSessionDriverTest.php b/tests/Unit/Http/Middleware/Api/SetSessionDriverTest.php
similarity index 96%
rename from tests/Unit/Http/Middleware/API/SetSessionDriverTest.php
rename to tests/Unit/Http/Middleware/Api/SetSessionDriverTest.php
index 68ed950cf..c41d742ef 100644
--- a/tests/Unit/Http/Middleware/API/SetSessionDriverTest.php
+++ b/tests/Unit/Http/Middleware/Api/SetSessionDriverTest.php
@@ -1,6 +1,6 @@
expectException(AuthenticationException::class);
+
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull();
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
diff --git a/tests/Unit/Http/Middleware/DaemonAuthenticateTest.php b/tests/Unit/Http/Middleware/DaemonAuthenticateTest.php
deleted file mode 100644
index 7329eb2d8..000000000
--- a/tests/Unit/Http/Middleware/DaemonAuthenticateTest.php
+++ /dev/null
@@ -1,78 +0,0 @@
-repository = m::mock(NodeRepositoryInterface::class);
- }
-
- /**
- * Test a valid daemon connection.
- */
- public function testValidDaemonConnection()
- {
- $this->setRequestRouteName('random.name');
- $node = factory(Node::class)->make();
-
- $this->request->shouldReceive('header')->with('X-Access-Node')->twice()->andReturn($node->daemonSecret);
-
- $this->repository->shouldReceive('findFirstWhere')->with(['daemonSecret' => $node->daemonSecret])->once()->andReturn($node);
-
- $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
- $this->assertRequestHasAttribute('node');
- $this->assertRequestAttributeEquals($node, 'node');
- }
-
- /**
- * Test that ignored routes do not continue through the middleware.
- */
- public function testIgnoredRouteShouldContinue()
- {
- $this->setRequestRouteName('daemon.configuration');
-
- $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
- $this->assertRequestMissingAttribute('node');
- }
-
- /**
- * Test that a request missing a X-Access-Node header causes an exception.
- *
- * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
- */
- public function testExceptionThrownIfMissingHeader()
- {
- $this->setRequestRouteName('random.name');
-
- $this->request->shouldReceive('header')->with('X-Access-Node')->once()->andReturn(false);
-
- $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
- }
-
- /**
- * Return an instance of the middleware using mocked dependencies.
- *
- * @return \Pterodactyl\Http\Middleware\DaemonAuthenticate
- */
- private function getMiddleware(): DaemonAuthenticate
- {
- return new DaemonAuthenticate($this->repository);
- }
-}
diff --git a/tests/Unit/Http/Middleware/Server/AccessingValidServerTest.php b/tests/Unit/Http/Middleware/Server/AccessingValidServerTest.php
index 564902889..5cedbd9b9 100644
--- a/tests/Unit/Http/Middleware/Server/AccessingValidServerTest.php
+++ b/tests/Unit/Http/Middleware/Server/AccessingValidServerTest.php
@@ -9,6 +9,8 @@ use Illuminate\Contracts\Routing\ResponseFactory;
use Tests\Unit\Http\Middleware\MiddlewareTestCase;
use Pterodactyl\Http\Middleware\Server\AccessingValidServer;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
+use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class AccessingValidServerTest extends MiddlewareTestCase
{
@@ -41,12 +43,12 @@ class AccessingValidServerTest extends MiddlewareTestCase
/**
* Test that an exception is thrown if the request is an API request and the server is suspended.
- *
- * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
- * @expectedExceptionMessage Server is suspended and cannot be accessed.
*/
public function testExceptionIsThrownIfServerIsSuspended()
{
+ $this->expectException(AccessDeniedHttpException::class);
+ $this->expectExceptionMessage('Server is suspended and cannot be accessed.');
+
$model = factory(Server::class)->make(['suspended' => 1]);
$this->request->shouldReceive('route->parameter')->with('server')->once()->andReturn('123456');
@@ -59,12 +61,12 @@ class AccessingValidServerTest extends MiddlewareTestCase
/**
* Test that an exception is thrown if the request is an API request and the server is not installed.
- *
- * @expectedException \Symfony\Component\HttpKernel\Exception\ConflictHttpException
- * @expectedExceptionMessage Server is still completing the installation process.
*/
public function testExceptionIsThrownIfServerIsNotInstalled()
{
+ $this->expectException(ConflictHttpException::class);
+ $this->expectExceptionMessage('Server is still completing the installation process.');
+
$model = factory(Server::class)->make(['installed' => 0]);
$this->request->shouldReceive('route->parameter')->with('server')->once()->andReturn('123456');
diff --git a/tests/Unit/Http/Middleware/Server/AuthenticateAsSubuserTest.php b/tests/Unit/Http/Middleware/Server/AuthenticateAsSubuserTest.php
deleted file mode 100644
index b24a2c227..000000000
--- a/tests/Unit/Http/Middleware/Server/AuthenticateAsSubuserTest.php
+++ /dev/null
@@ -1,71 +0,0 @@
-keyProviderService = m::mock(DaemonKeyProviderService::class);
- }
-
- /**
- * Test a successful instance of the middleware.
- */
- public function testSuccessfulMiddleware()
- {
- $model = factory(Server::class)->make();
- $user = $this->setRequestUser();
- $this->setRequestAttribute('server', $model);
-
- $this->keyProviderService->shouldReceive('handle')->with($model, $user)->once()->andReturn('abc123');
-
- $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
- $this->assertRequestHasAttribute('server_token');
- $this->assertRequestAttributeEquals('abc123', 'server_token');
- }
-
- /**
- * Test middleware handles missing token exception.
- *
- * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
- * @expectedExceptionMessage This account does not have permission to access this server.
- */
- public function testExceptionIsThrownIfNoTokenIsFound()
- {
- $model = factory(Server::class)->make();
- $user = $this->setRequestUser();
- $this->setRequestAttribute('server', $model);
-
- $this->keyProviderService->shouldReceive('handle')->with($model, $user)->once()->andThrow(new RecordNotFoundException);
-
- $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
- }
-
- /**
- * Return an instance of the middleware using mocked dependencies.
- *
- * @return \Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser
- */
- public function getMiddleware(): AuthenticateAsSubuser
- {
- return new AuthenticateAsSubuser($this->keyProviderService);
- }
-}
diff --git a/tests/Unit/Http/Middleware/Server/DatabaseBelongsToServerTest.php b/tests/Unit/Http/Middleware/Server/DatabaseBelongsToServerTest.php
deleted file mode 100644
index 0eed6945d..000000000
--- a/tests/Unit/Http/Middleware/Server/DatabaseBelongsToServerTest.php
+++ /dev/null
@@ -1,92 +0,0 @@
-repository = m::mock(DatabaseRepositoryInterface::class);
- }
-
- /**
- * Test a successful middleware instance.
- */
- public function testSuccessfulMiddleware()
- {
- $model = factory(Server::class)->make();
- $database = factory(Database::class)->make([
- 'server_id' => $model->id,
- ]);
- $this->setRequestAttribute('server', $model);
-
- $this->request->shouldReceive('input')->with('database')->once()->andReturn($database->id);
- $this->repository->shouldReceive('find')->with($database->id)->once()->andReturn($database);
-
- $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
- $this->assertRequestHasAttribute('database');
- $this->assertRequestAttributeEquals($database, 'database');
- }
-
- /**
- * Test that an exception is thrown if no database record is found.
- *
- * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
- */
- public function testExceptionIsThrownIfNoDatabaseRecordFound()
- {
- $model = factory(Server::class)->make();
- $database = factory(Database::class)->make();
- $this->setRequestAttribute('server', $model);
-
- $this->request->shouldReceive('input')->with('database')->once()->andReturn($database->id);
- $this->repository->shouldReceive('find')->with($database->id)->once()->andReturnNull();
-
- $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
- }
-
- /**
- * Test that an exception is found if the database server does not match the
- * request server.
- *
- * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
- */
- public function testExceptionIsThrownIfDatabaseServerDoesNotMatchCurrent()
- {
- $model = factory(Server::class)->make();
- $database = factory(Database::class)->make();
- $this->setRequestAttribute('server', $model);
-
- $this->request->shouldReceive('input')->with('database')->once()->andReturn($database->id);
- $this->repository->shouldReceive('find')->with($database->id)->once()->andReturn($database);
-
- $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
- }
-
- /**
- * Return an instance of the middleware using mocked dependencies.
- *
- * @return \Pterodactyl\Http\Middleware\Server\DatabaseBelongsToServer
- */
- private function getMiddleware(): DatabaseBelongsToServer
- {
- return new DatabaseBelongsToServer($this->repository);
- }
-}
diff --git a/tests/Unit/Http/Middleware/Server/ScheduleBelongsToServerTest.php b/tests/Unit/Http/Middleware/Server/ScheduleBelongsToServerTest.php
deleted file mode 100644
index ac455a84a..000000000
--- a/tests/Unit/Http/Middleware/Server/ScheduleBelongsToServerTest.php
+++ /dev/null
@@ -1,81 +0,0 @@
-hashids = m::mock(HashidsInterface::class);
- $this->repository = m::mock(ScheduleRepositoryInterface::class);
- }
-
- /**
- * Test a successful middleware instance.
- */
- public function testSuccessfulMiddleware()
- {
- $model = factory(Server::class)->make();
- $schedule = factory(Schedule::class)->make([
- 'server_id' => $model->id,
- ]);
- $this->setRequestAttribute('server', $model);
-
- $this->request->shouldReceive('route->parameter')->with('schedule')->once()->andReturn('abc123');
- $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($schedule->id);
- $this->repository->shouldReceive('getScheduleWithTasks')->with($schedule->id)->once()->andReturn($schedule);
-
- $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
- $this->assertRequestHasAttribute('schedule');
- $this->assertRequestAttributeEquals($schedule, 'schedule');
- }
-
- /**
- * Test that an exception is thrown if the schedule does not belong to
- * the request server.
- *
- * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
- */
- public function testExceptionIsThrownIfScheduleDoesNotBelongToServer()
- {
- $model = factory(Server::class)->make();
- $schedule = factory(Schedule::class)->make();
- $this->setRequestAttribute('server', $model);
-
- $this->request->shouldReceive('route->parameter')->with('schedule')->once()->andReturn('abc123');
- $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($schedule->id);
- $this->repository->shouldReceive('getScheduleWithTasks')->with($schedule->id)->once()->andReturn($schedule);
-
- $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
- }
-
- /**
- * Return an instance of the middleware using mocked dependencies.
- *
- * @return \Pterodactyl\Http\Middleware\Server\ScheduleBelongsToServer
- */
- private function getMiddleware(): ScheduleBelongsToServer
- {
- return new ScheduleBelongsToServer($this->hashids, $this->repository);
- }
-}
diff --git a/tests/Unit/Http/Middleware/Server/SubuserBelongsToServerTest.php b/tests/Unit/Http/Middleware/Server/SubuserBelongsToServerTest.php
deleted file mode 100644
index 7d06ece59..000000000
--- a/tests/Unit/Http/Middleware/Server/SubuserBelongsToServerTest.php
+++ /dev/null
@@ -1,156 +0,0 @@
-hashids = m::mock(HashidsInterface::class);
- $this->repository = m::mock(SubuserRepositoryInterface::class);
- }
-
- /**
- * Test a successful middleware instance.
- */
- public function testSuccessfulMiddleware()
- {
- $model = factory(Server::class)->make();
- $subuser = factory(Subuser::class)->make([
- 'server_id' => $model->id,
- ]);
- $this->setRequestAttribute('server', $model);
-
- $this->request->shouldReceive('route->parameter')->with('subuser', 0)->once()->andReturn('abc123');
- $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($subuser->id);
- $this->repository->shouldReceive('find')->with($subuser->id)->once()->andReturn($subuser);
-
- $this->request->shouldReceive('method')->withNoArgs()->once()->andReturn('GET');
-
- $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
- $this->assertRequestHasAttribute('subuser');
- $this->assertRequestAttributeEquals($subuser, 'subuser');
- }
-
- /**
- * Test that a user can edit a user other than themselves.
- */
- public function testSuccessfulMiddlewareWhenPatchRequest()
- {
- $this->setRequestUser();
- $model = factory(Server::class)->make();
- $subuser = factory(Subuser::class)->make([
- 'server_id' => $model->id,
- ]);
- $this->setRequestAttribute('server', $model);
-
- $this->request->shouldReceive('route->parameter')->with('subuser', 0)->once()->andReturn('abc123');
- $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($subuser->id);
- $this->repository->shouldReceive('find')->with($subuser->id)->once()->andReturn($subuser);
-
- $this->request->shouldReceive('method')->withNoArgs()->once()->andReturn('PATCH');
-
- $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
- $this->assertRequestHasAttribute('subuser');
- $this->assertRequestAttributeEquals($subuser, 'subuser');
- }
-
- /**
- * Test that an exception is thrown if a user attempts to edit themself.
- */
- public function testExceptionIsThrownIfUserTriesToEditSelf()
- {
- $user = $this->setRequestUser();
- $model = factory(Server::class)->make();
- $subuser = factory(Subuser::class)->make([
- 'server_id' => $model->id,
- 'user_id' => $user->id,
- ]);
- $this->setRequestAttribute('server', $model);
-
- $this->request->shouldReceive('route->parameter')->with('subuser', 0)->once()->andReturn('abc123');
- $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($subuser->id);
- $this->repository->shouldReceive('find')->with($subuser->id)->once()->andReturn($subuser);
-
- $this->request->shouldReceive('method')->withNoArgs()->once()->andReturn('PATCH');
-
- try {
- $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
- } catch (PterodactylException $exception) {
- $this->assertInstanceOf(DisplayException::class, $exception);
- $this->assertEquals(trans('exceptions.subusers.editing_self'), $exception->getMessage());
- }
- }
-
- /**
- * Test that an exception is thrown if a subuser server does not match the
- * request server.
- *
- * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
- */
- public function testExceptionIsThrownIfSubuserServerDoesNotMatchRequestServer()
- {
- $model = factory(Server::class)->make();
- $subuser = factory(Subuser::class)->make();
- $this->setRequestAttribute('server', $model);
-
- $this->request->shouldReceive('route->parameter')->with('subuser', 0)->once()->andReturn('abc123');
- $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($subuser->id);
- $this->repository->shouldReceive('find')->with($subuser->id)->once()->andReturn($subuser);
-
- $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
- }
-
- /**
- * Test that an exception is thrown if no subuser is found.
- *
- * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
- */
- public function testExceptionIsThrownIfNoSubuserIsFound()
- {
- $model = factory(Server::class)->make();
- $subuser = factory(Subuser::class)->make();
- $this->setRequestAttribute('server', $model);
-
- $this->request->shouldReceive('route->parameter')->with('subuser', 0)->once()->andReturn('abc123');
- $this->hashids->shouldReceive('decodeFirst')->with('abc123', 0)->once()->andReturn($subuser->id);
- $this->repository->shouldReceive('find')->with($subuser->id)->once()->andReturnNull();
-
- $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
- }
-
- /**
- * Return an instance of the middleware using mocked dependencies.
- *
- * @return \Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer
- */
- private function getMiddleware(): SubuserBelongsToServer
- {
- return new SubuserBelongsToServer($this->hashids, $this->repository);
- }
-}
diff --git a/tests/Unit/Jobs/Schedule/RunTaskJobTest.php b/tests/Unit/Jobs/Schedule/RunTaskJobTest.php
index adfe0b54f..4d7688a82 100644
--- a/tests/Unit/Jobs/Schedule/RunTaskJobTest.php
+++ b/tests/Unit/Jobs/Schedule/RunTaskJobTest.php
@@ -3,53 +3,51 @@
namespace Tests\Unit\Jobs\Schedule;
use Mockery as m;
+use Carbon\Carbon;
use Tests\TestCase;
use Cake\Chronos\Chronos;
use Pterodactyl\Models\Task;
use Pterodactyl\Models\User;
use GuzzleHttp\Psr7\Response;
+use InvalidArgumentException;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Schedule;
use Illuminate\Support\Facades\Bus;
use Pterodactyl\Jobs\Schedule\RunTaskJob;
-use Illuminate\Contracts\Config\Repository;
+use Pterodactyl\Repositories\Eloquent\TaskRepository;
+use Pterodactyl\Services\Backups\InitiateBackupService;
+use Pterodactyl\Repositories\Eloquent\ScheduleRepository;
+use Pterodactyl\Repositories\Wings\DaemonPowerRepository;
+use Pterodactyl\Repositories\Wings\DaemonCommandRepository;
use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
-use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService;
use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface;
-use Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface;
-use Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface;
class RunTaskJobTest extends TestCase
{
/**
- * @var \Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
- protected $commandRepository;
+ private $commandRepository;
/**
- * @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
- protected $config;
+ private $powerRepository;
/**
- * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
- protected $keyProviderService;
+ private $initiateBackupService;
/**
- * @var \Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
- protected $powerRepository;
+ private $taskRepository;
/**
- * @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
- protected $scheduleRepository;
-
- /**
- * @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface|\Mockery\Mock
- */
- protected $taskRepository;
+ private $scheduleRepository;
/**
* Setup tests.
@@ -57,17 +55,16 @@ class RunTaskJobTest extends TestCase
public function setUp(): void
{
parent::setUp();
+
Bus::fake();
- Chronos::setTestNow(Chronos::now());
+ Carbon::setTestNow(Carbon::now());
- $this->commandRepository = m::mock(CommandRepositoryInterface::class);
- $this->config = m::mock(Repository::class);
- $this->keyProviderService = m::mock(DaemonKeyProviderService::class);
- $this->powerRepository = m::mock(PowerRepositoryInterface::class);
- $this->scheduleRepository = m::mock(ScheduleRepositoryInterface::class);
- $this->taskRepository = m::mock(TaskRepositoryInterface::class);
+ $this->commandRepository = m::mock(DaemonCommandRepository::class);
+ $this->powerRepository = m::mock(DaemonPowerRepository::class);
+ $this->taskRepository = m::mock(TaskRepository::class);
+ $this->initiateBackupService = m::mock(InitiateBackupService::class);
+ $this->scheduleRepository = m::mock(ScheduleRepository::class);
- $this->app->instance(Repository::class, $this->config);
$this->app->instance(TaskRepositoryInterface::class, $this->taskRepository);
$this->app->instance(ScheduleRepositoryInterface::class, $this->scheduleRepository);
}
@@ -77,17 +74,20 @@ class RunTaskJobTest extends TestCase
*/
public function testPowerAction()
{
- $schedule = factory(Schedule::class)->make();
+ /** @var \Pterodactyl\Models\Schedule $schedule */
+ $schedule = factory(Schedule::class)->make(['is_active' => true]);
+
+ /** @var \Pterodactyl\Models\Task $task */
$task = factory(Task::class)->make(['action' => 'power', 'sequence_id' => 1]);
+
+ /* @var \Pterodactyl\Models\Server $server */
$task->setRelation('server', $server = factory(Server::class)->make());
$task->setRelation('schedule', $schedule);
$server->setRelation('user', factory(User::class)->make());
- $this->taskRepository->shouldReceive('getTaskForJobProcess')->with($task->id)->once()->andReturn($task);
- $this->keyProviderService->shouldReceive('handle')->with($server, $server->user)->once()->andReturn('123456');
- $this->powerRepository->shouldReceive('setServer')->with($task->server)->once()->andReturnSelf()
- ->shouldReceive('setToken')->with('123456')->once()->andReturnSelf()
- ->shouldReceive('sendSignal')->with($task->payload)->once()->andReturn(new Response);
+ $this->taskRepository->expects('getTaskForJobProcess')->with($task->id)->andReturn($task);
+ $this->powerRepository->expects('setServer')->with($task->server)->andReturnSelf()
+ ->getMock()->expects('send')->with($task->payload)->andReturn(new Response);
$this->taskRepository->shouldReceive('update')->with($task->id, ['is_queued' => false])->once()->andReturnNull();
$this->taskRepository->shouldReceive('getNextTask')->with($schedule->id, $task->sequence_id)->once()->andReturnNull();
@@ -113,14 +113,12 @@ class RunTaskJobTest extends TestCase
$task->setRelation('schedule', $schedule);
$server->setRelation('user', factory(User::class)->make());
- $this->taskRepository->shouldReceive('getTaskForJobProcess')->with($task->id)->once()->andReturn($task);
- $this->keyProviderService->shouldReceive('handle')->with($server, $server->user)->once()->andReturn('123456');
- $this->commandRepository->shouldReceive('setServer')->with($task->server)->once()->andReturnSelf()
- ->shouldReceive('setToken')->with('123456')->once()->andReturnSelf()
- ->shouldReceive('send')->with($task->payload)->once()->andReturn(new Response);
+ $this->taskRepository->expects('getTaskForJobProcess')->with($task->id)->andReturn($task);
+ $this->commandRepository->expects('setServer')->with($task->server)->andReturnSelf()
+ ->getMock()->expects('send')->with($task->payload)->andReturn(new Response);
- $this->taskRepository->shouldReceive('update')->with($task->id, ['is_queued' => false])->once()->andReturnNull();
- $this->taskRepository->shouldReceive('getNextTask')->with($schedule->id, $task->sequence_id)->once()->andReturnNull();
+ $this->taskRepository->expects('update')->with($task->id, ['is_queued' => false])->andReturnNull();
+ $this->taskRepository->expects('getNextTask')->with($schedule->id, $task->sequence_id)->andReturnNull();
$this->scheduleRepository->shouldReceive('withoutFreshModel->update')->with($schedule->id, [
'is_processing' => false,
@@ -143,19 +141,17 @@ class RunTaskJobTest extends TestCase
$task->setRelation('schedule', $schedule);
$server->setRelation('user', factory(User::class)->make());
- $this->taskRepository->shouldReceive('getTaskForJobProcess')->with($task->id)->once()->andReturn($task);
- $this->keyProviderService->shouldReceive('handle')->with($server, $server->user)->once()->andReturn('123456');
- $this->commandRepository->shouldReceive('setServer')->with($task->server)->once()->andReturnSelf()
- ->shouldReceive('setToken')->with('123456')->once()->andReturnSelf()
- ->shouldReceive('send')->with($task->payload)->once()->andReturn(new Response);
+ $this->taskRepository->expects('getTaskForJobProcess')->with($task->id)->andReturn($task);
+ $this->commandRepository->expects('setServer')->with($task->server)->andReturnSelf()
+ ->getMock()->expects('send')->with($task->payload)->andReturn(new Response);
$this->taskRepository->shouldReceive('update')->with($task->id, ['is_queued' => false])->once()->andReturnNull();
$nextTask = factory(Task::class)->make();
- $this->taskRepository->shouldReceive('getNextTask')->with($schedule->id, $task->sequence_id)->once()->andReturn($nextTask);
- $this->taskRepository->shouldReceive('update')->with($nextTask->id, [
+ $this->taskRepository->expects('getNextTask')->with($schedule->id, $task->sequence_id)->andReturn($nextTask);
+ $this->taskRepository->expects('update')->with($nextTask->id, [
'is_queued' => true,
- ])->once()->andReturnNull();
+ ])->andReturnNull();
$this->getJobInstance($task->id, $schedule->id);
@@ -170,19 +166,19 @@ class RunTaskJobTest extends TestCase
/**
* Test that an exception is thrown if an invalid task action is supplied.
- *
- * @expectedException \InvalidArgumentException
- * @expectedExceptionMessage Cannot run a task that points to a non-existent action.
*/
public function testInvalidActionPassedToJob()
{
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Cannot run a task that points to a non-existent action.');
+
$schedule = factory(Schedule::class)->make();
$task = factory(Task::class)->make(['action' => 'invalid', 'sequence_id' => 1]);
$task->setRelation('server', $server = factory(Server::class)->make());
$task->setRelation('schedule', $schedule);
$server->setRelation('user', factory(User::class)->make());
- $this->taskRepository->shouldReceive('getTaskForJobProcess')->with($task->id)->once()->andReturn($task);
+ $this->taskRepository->expects('getTaskForJobProcess')->with($task->id)->andReturn($task);
$this->getJobInstance($task->id, 1234);
}
@@ -218,14 +214,12 @@ class RunTaskJobTest extends TestCase
* @param int $schedule
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
- * @throws \Pterodactyl\Exceptions\Repository\Daemon\InvalidPowerSignalException
- * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
private function getJobInstance($task, $schedule)
{
return (new RunTaskJob($task, $schedule))->handle(
$this->commandRepository,
- $this->keyProviderService,
+ $this->initiateBackupService,
$this->powerRepository,
$this->taskRepository
);
diff --git a/tests/Unit/Services/Allocations/AllocationDeletionServiceTest.php b/tests/Unit/Services/Allocations/AllocationDeletionServiceTest.php
index f1ad8eb68..521aed20a 100644
--- a/tests/Unit/Services/Allocations/AllocationDeletionServiceTest.php
+++ b/tests/Unit/Services/Allocations/AllocationDeletionServiceTest.php
@@ -7,6 +7,7 @@ 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
{
@@ -37,11 +38,11 @@ class AllocationDeletionServiceTest extends TestCase
/**
* Test that an exception gets thrown if an allocation is currently assigned to a server.
- *
- * @expectedException \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException
*/
public function testExceptionThrownIfAssignedToServer()
{
+ $this->expectException(ServerUsingAllocationException::class);
+
$model = factory(Allocation::class)->make(['server_id' => 123]);
$this->getService()->handle($model);
diff --git a/tests/Unit/Services/Allocations/AssignmentServiceTest.php b/tests/Unit/Services/Allocations/AssignmentServiceTest.php
index 7449c81cc..0e6da9035 100644
--- a/tests/Unit/Services/Allocations/AssignmentServiceTest.php
+++ b/tests/Unit/Services/Allocations/AssignmentServiceTest.php
@@ -8,6 +8,10 @@ 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
{
@@ -190,12 +194,12 @@ class AssignmentServiceTest extends TestCase
/**
* Test that a CIDR IP address with a range works properly.
- *
- * @expectedException \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException
- * @expectedExceptionMessage CIDR notation only allows masks between /25 and /32.
*/
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'],
@@ -206,12 +210,12 @@ class AssignmentServiceTest extends TestCase
/**
* Test that an exception is thrown if there are too many ports.
- *
- * @expectedException \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException
- * @expectedExceptionMessage Adding more than 1000 ports in a single range at once is not supported.
*/
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'],
@@ -224,12 +228,12 @@ class AssignmentServiceTest extends TestCase
/**
* Test that an exception is thrown if an invalid port is provided.
- *
- * @expectedException \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException
- * @expectedExceptionMessage The mapping provided for test123 was invalid and could not be processed.
*/
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'],
@@ -245,11 +249,12 @@ class AssignmentServiceTest extends TestCase
* @param array $ports
*
* @dataProvider invalidPortsDataProvider
- * @expectedException \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException
- * @expectedExceptionMessage Ports in an allocation must be greater than 1024 and less than or equal to 65535.
*/
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();
diff --git a/tests/Unit/Services/Allocations/SetDefaultAllocationServiceTest.php b/tests/Unit/Services/Allocations/SetDefaultAllocationServiceTest.php
deleted file mode 100644
index 75d1f32dd..000000000
--- a/tests/Unit/Services/Allocations/SetDefaultAllocationServiceTest.php
+++ /dev/null
@@ -1,156 +0,0 @@
-connection = m::mock(ConnectionInterface::class);
- $this->daemonRepository = m::mock(DaemonRepositoryInterface::class);
- $this->repository = m::mock(AllocationRepositoryInterface::class);
- $this->serverRepository = m::mock(ServerRepositoryInterface::class);
- }
-
- /**
- * Test that an allocation can be updated.
- *
- * @dataProvider useModelDataProvider
- */
- public function testAllocationIsUpdated(bool $useModel)
- {
- $allocations = factory(Allocation::class)->times(2)->make();
- $model = factory(Server::class)->make();
- if (! $useModel) {
- $this->serverRepository->shouldReceive('find')->with(1234)->once()->andReturn($model);
- }
-
- $this->repository->shouldReceive('findWhere')->with([['server_id', '=', $model->id]])->once()->andReturn($allocations);
- $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
- $this->serverRepository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf();
- $this->serverRepository->shouldReceive('update')->with($model->id, [
- 'allocation_id' => $allocations->first()->id,
- ])->once()->andReturn(new Response);
-
- $this->daemonRepository->shouldReceive('setServer')->with($model)->once()->andReturnSelf();
- $this->daemonRepository->shouldReceive('update')->with([
- 'build' => [
- 'default' => [
- 'ip' => $allocations->first()->ip,
- 'port' => $allocations->first()->port,
- ],
- 'ports|overwrite' => $allocations->groupBy('ip')->map(function ($item) {
- return $item->pluck('port');
- })->toArray(),
- ],
- ])->once()->andReturn(new Response);
- $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
-
- $response = $this->getService()->handle($useModel ? $model : 1234, $allocations->first()->id);
- $this->assertNotEmpty($response);
- $this->assertSame($allocations->first(), $response);
- }
-
- /**
- * Test that an allocation that doesn't belong to a server throws an exception.
- *
- * @expectedException \Pterodactyl\Exceptions\Service\Allocation\AllocationDoesNotBelongToServerException
- */
- public function testAllocationNotBelongingToServerThrowsException()
- {
- $model = factory(Server::class)->make();
- $this->repository->shouldReceive('findWhere')->with([['server_id', '=', $model->id]])->once()->andReturn(collect());
-
- $this->getService()->handle($model, 1234);
- }
-
- /**
- * Test that an exception thrown by guzzle is handled properly.
- */
- public function testExceptionThrownByGuzzleIsHandled()
- {
- $this->configureExceptionMock();
-
- $allocation = factory(Allocation::class)->make();
- $model = factory(Server::class)->make();
-
- $this->repository->shouldReceive('findWhere')->with([['server_id', '=', $model->id]])->once()->andReturn(collect([$allocation]));
- $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
- $this->serverRepository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf();
- $this->serverRepository->shouldReceive('update')->with($model->id, [
- 'allocation_id' => $allocation->id,
- ])->once()->andReturn(new Response);
-
- $this->daemonRepository->shouldReceive('setServer->update')->once()->andThrow($this->getExceptionMock());
- $this->connection->shouldReceive('rollBack')->withNoArgs()->once()->andReturnNull();
-
- try {
- $this->getService()->handle($model, $allocation->id);
- } catch (PterodactylException $exception) {
- $this->assertInstanceOf(DaemonConnectionException::class, $exception);
- $this->assertInstanceOf(RequestException::class, $exception->getPrevious());
- }
- }
-
- /**
- * Data provider to determine if a model should be passed or an int.
- *
- * @return array
- */
- public function useModelDataProvider(): array
- {
- return [[false], [true]];
- }
-
- /**
- * Return an instance of the service with mocked dependencies.
- *
- * @return \Pterodactyl\Services\Allocations\SetDefaultAllocationService
- */
- private function getService(): SetDefaultAllocationService
- {
- return new SetDefaultAllocationService($this->repository, $this->connection, $this->daemonRepository, $this->serverRepository);
- }
-}
diff --git a/tests/Unit/Services/DaemonKeys/DaemonKeyCreationServiceTest.php b/tests/Unit/Services/DaemonKeys/DaemonKeyCreationServiceTest.php
deleted file mode 100644
index 7c5bad2c2..000000000
--- a/tests/Unit/Services/DaemonKeys/DaemonKeyCreationServiceTest.php
+++ /dev/null
@@ -1,98 +0,0 @@
-.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-namespace Tests\Unit\Services\DaemonKeys;
-
-use Mockery as m;
-use Carbon\Carbon;
-use Tests\TestCase;
-use phpmock\phpunit\PHPMock;
-use Illuminate\Contracts\Config\Repository;
-use Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService;
-use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface;
-
-class DaemonKeyCreationServiceTest extends TestCase
-{
- use PHPMock;
-
- /**
- * @var \Carbon\Carbon|\Mockery\Mock
- */
- protected $carbon;
-
- /**
- * @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock
- */
- protected $config;
-
- /**
- * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface|\Mockery\Mock
- */
- protected $repository;
-
- /**
- * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService
- */
- protected $service;
-
- /**
- * Setup tests.
- */
- public function setUp(): void
- {
- parent::setUp();
-
- $this->carbon = m::mock(Carbon::class);
- $this->config = m::Mock(Repository::class);
- $this->repository = m::mock(DaemonKeyRepositoryInterface::class);
-
- $this->service = new DaemonKeyCreationService($this->carbon, $this->config, $this->repository);
- }
-
- /**
- * Test that a daemon key is created.
- */
- public function testDaemonKeyIsCreated()
- {
- $this->getFunctionMock('\\Pterodactyl\\Services\\DaemonKeys', 'str_random')
- ->expects($this->once())->willReturn('random_string');
-
- $this->config->shouldReceive('get')->with('pterodactyl.api.key_expire_time')->once()->andReturn(100);
- $this->carbon->shouldReceive('now')->withNoArgs()->once()->andReturnSelf()
- ->shouldReceive('addMinutes')->with(100)->once()->andReturnSelf()
- ->shouldReceive('toDateTimeString')->withNoArgs()->once()->andReturn('00:00:00');
-
- $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf()
- ->shouldReceive('create')->with([
- 'user_id' => 1,
- 'server_id' => 2,
- 'secret' => DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . 'random_string',
- 'expires_at' => '00:00:00',
- ])->once()->andReturnNull();
-
- $response = $this->service->handle(2, 1);
- $this->assertNotEmpty($response);
- $this->assertEquals('i_random_string', $response);
- }
-}
diff --git a/tests/Unit/Services/DaemonKeys/DaemonKeyProviderServiceTest.php b/tests/Unit/Services/DaemonKeys/DaemonKeyProviderServiceTest.php
deleted file mode 100644
index 74c34d3c0..000000000
--- a/tests/Unit/Services/DaemonKeys/DaemonKeyProviderServiceTest.php
+++ /dev/null
@@ -1,229 +0,0 @@
-.
- *
- * This software is licensed under the terms of the MIT license.
- * https://opensource.org/licenses/MIT
- */
-
-namespace Tests\Unit\Services\DaemonKeys;
-
-use Mockery as m;
-use Carbon\Carbon;
-use Tests\TestCase;
-use Pterodactyl\Models\User;
-use Pterodactyl\Models\Server;
-use Pterodactyl\Models\Subuser;
-use Pterodactyl\Models\DaemonKey;
-use Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService;
-use Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService;
-use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService;
-use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
-use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
-use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface;
-
-class DaemonKeyProviderServiceTest extends TestCase
-{
- /**
- * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService|\Mockery\Mock
- */
- private $keyCreationService;
-
- /**
- * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService|\Mockery\Mock
- */
- private $keyUpdateService;
-
- /**
- * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface|\Mockery\Mock
- */
- private $repository;
-
- /**
- * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface|\Mockery\Mock
- */
- private $subuserRepository;
-
- /**
- * Setup tests.
- */
- public function setUp(): void
- {
- parent::setUp();
- Carbon::setTestNow(Carbon::now());
-
- $this->keyCreationService = m::mock(DaemonKeyCreationService::class);
- $this->keyUpdateService = m::mock(DaemonKeyUpdateService::class);
- $this->repository = m::mock(DaemonKeyRepositoryInterface::class);
- $this->subuserRepository = m::mock(SubuserRepositoryInterface::class);
- }
-
- /**
- * Test that a key is returned correctly as a non-admin.
- */
- public function testKeyIsReturned()
- {
- $server = factory(Server::class)->make();
- $user = factory(User::class)->make();
- $key = factory(DaemonKey::class)->make();
-
- $this->repository->shouldReceive('findFirstWhere')->with([
- ['user_id', '=', $user->id],
- ['server_id', '=', $server->id],
- ])->once()->andReturn($key);
-
- $response = $this->getService()->handle($server, $user);
- $this->assertNotEmpty($response);
- $this->assertEquals($key->secret, $response);
- }
-
- /**
- * Test that an expired key is updated and then returned.
- */
- public function testExpiredKeyIsUpdated()
- {
- $server = factory(Server::class)->make();
- $user = factory(User::class)->make(['root_admin' => 0]);
- $key = factory(DaemonKey::class)->make(['expires_at' => Carbon::now()->subHour()]);
-
- $this->repository->shouldReceive('findFirstWhere')->with([
- ['user_id', '=', $user->id],
- ['server_id', '=', $server->id],
- ])->once()->andReturn($key);
-
- $this->keyUpdateService->shouldReceive('handle')->with($key->id)->once()->andReturn('abc123');
-
- $response = $this->getService()->handle($server, $user);
- $this->assertNotEmpty($response);
- $this->assertEquals('abc123', $response);
- }
-
- /**
- * Test that an expired key is not updated and the expired key is returned.
- */
- public function testExpiredKeyIsNotUpdated()
- {
- $server = factory(Server::class)->make();
- $user = factory(User::class)->make(['root_admin' => 0]);
- $key = factory(DaemonKey::class)->make(['expires_at' => Carbon::now()->subHour()]);
-
- $this->repository->shouldReceive('findFirstWhere')->with([
- ['user_id', '=', $user->id],
- ['server_id', '=', $server->id],
- ])->once()->andReturn($key);
-
- $response = $this->getService()->handle($server, $user, false);
- $this->assertNotEmpty($response);
- $this->assertEquals($key->secret, $response);
- }
-
- /**
- * Test that a key is created if it is missing and the user is a
- * root administrator.
- */
- public function testMissingKeyIsCreatedIfRootAdmin()
- {
- $server = factory(Server::class)->make();
- $user = factory(User::class)->make(['root_admin' => 1]);
- $key = factory(DaemonKey::class)->make(['expires_at' => Carbon::now()->subHour()]);
-
- $this->repository->shouldReceive('findFirstWhere')->with([
- ['user_id', '=', $user->id],
- ['server_id', '=', $server->id],
- ])->once()->andThrow(new RecordNotFoundException);
-
- $this->keyCreationService->shouldReceive('handle')->with($server->id, $user->id)->once()->andReturn($key->secret);
-
- $response = $this->getService()->handle($server, $user, false);
- $this->assertNotEmpty($response);
- $this->assertEquals($key->secret, $response);
- }
-
- /**
- * Test that a key is created if it is missing and the user is the
- * server owner.
- */
- public function testMissingKeyIsCreatedIfUserIsServerOwner()
- {
- $user = factory(User::class)->make(['root_admin' => 0]);
- $server = factory(Server::class)->make(['owner_id' => $user->id]);
- $key = factory(DaemonKey::class)->make(['expires_at' => Carbon::now()->subHour()]);
-
- $this->repository->shouldReceive('findFirstWhere')->with([
- ['user_id', '=', $user->id],
- ['server_id', '=', $server->id],
- ])->once()->andThrow(new RecordNotFoundException);
-
- $this->keyCreationService->shouldReceive('handle')->with($server->id, $user->id)->once()->andReturn($key->secret);
-
- $response = $this->getService()->handle($server, $user, false);
- $this->assertNotEmpty($response);
- $this->assertEquals($key->secret, $response);
- }
-
- /**
- * Test that a missing key is created for a subuser.
- */
- public function testMissingKeyIsCreatedForSubuser()
- {
- $user = factory(User::class)->make(['root_admin' => 0]);
- $server = factory(Server::class)->make();
- $key = factory(DaemonKey::class)->make(['expires_at' => Carbon::now()->subHour()]);
- $subuser = factory(Subuser::class)->make(['user_id' => $user->id, 'server_id' => $server->id]);
-
- $this->repository->shouldReceive('findFirstWhere')->with([
- ['user_id', '=', $user->id],
- ['server_id', '=', $server->id],
- ])->once()->andThrow(new RecordNotFoundException);
-
- $this->subuserRepository->shouldReceive('findFirstWhere')->once()->with([
- ['user_id', '=', $user->id],
- ['server_id', '=', $server->id],
- ])->andReturn($subuser);
-
- $this->keyCreationService->shouldReceive('handle')->with($server->id, $user->id)->once()->andReturn($key->secret);
-
- $response = $this->getService()->handle($server, $user, false);
- $this->assertNotEmpty($response);
- $this->assertEquals($key->secret, $response);
- }
-
- /**
- * Test that an exception is thrown if the user should not get a key.
- *
- * @expectedException \Pterodactyl\Exceptions\Repository\RecordNotFoundException
- */
- public function testExceptionIsThrownIfUserDoesNotDeserveKey()
- {
- $server = factory(Server::class)->make();
- $user = factory(User::class)->make(['root_admin' => 0]);
-
- $this->repository->shouldReceive('findFirstWhere')->with([
- ['user_id', '=', $user->id],
- ['server_id', '=', $server->id],
- ])->once()->andThrow(new RecordNotFoundException);
-
- $this->subuserRepository->shouldReceive('findFirstWhere')->once()->with([
- ['user_id', '=', $user->id],
- ['server_id', '=', $server->id],
- ])->andThrow(new RecordNotFoundException);
-
- $this->getService()->handle($server, $user, false);
- }
-
- /**
- * Return an instance of the service with mocked dependencies.
- *
- * @return \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService
- */
- private function getService(): DaemonKeyProviderService
- {
- return new DaemonKeyProviderService(
- $this->keyCreationService,
- $this->repository,
- $this->keyUpdateService,
- $this->subuserRepository
- );
- }
-}
diff --git a/tests/Unit/Services/DaemonKeys/DaemonKeyUpdateServiceTest.php b/tests/Unit/Services/DaemonKeys/DaemonKeyUpdateServiceTest.php
deleted file mode 100644
index b1beadbce..000000000
--- a/tests/Unit/Services/DaemonKeys/DaemonKeyUpdateServiceTest.php
+++ /dev/null
@@ -1,83 +0,0 @@
-.
- *
- * This software is licensed under the terms of the MIT license.
- * https://opensource.org/licenses/MIT
- */
-
-namespace Tests\Unit\Services\DaemonKeys;
-
-use Mockery as m;
-use Carbon\Carbon;
-use Tests\TestCase;
-use phpmock\phpunit\PHPMock;
-use Illuminate\Contracts\Config\Repository;
-use Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService;
-use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface;
-
-class DaemonKeyUpdateServiceTest extends TestCase
-{
- use PHPMock;
-
- /**
- * @var \Carbon\Carbon|\Mockery\Mock
- */
- protected $carbon;
-
- /**
- * @var \Illuminate\Contracts\Config\Repository|\Mockery\Mock
- */
- protected $config;
-
- /**
- * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface|\Mockery\Mock
- */
- protected $repository;
-
- /**
- * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService
- */
- protected $service;
-
- /**
- * Setup tests.
- */
- public function setUp(): void
- {
- parent::setUp();
-
- $this->carbon = m::Mock(Carbon::class);
- $this->config = m::mock(Repository::class);
- $this->repository = m::mock(DaemonKeyRepositoryInterface::class);
-
- $this->service = new DaemonKeyUpdateService($this->carbon, $this->config, $this->repository);
- }
-
- /**
- * Test that a key is updated.
- */
- public function testKeyIsUpdated()
- {
- $secret = DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . 'random_string';
-
- $this->getFunctionMock('\\Pterodactyl\\Services\\DaemonKeys', 'str_random')
- ->expects($this->once())->with(40)->willReturn('random_string');
-
- $this->config->shouldReceive('get')->with('pterodactyl.api.key_expire_time')->once()->andReturn(100);
- $this->carbon->shouldReceive('now')->withNoArgs()->once()->andReturnSelf()
- ->shouldReceive('addMinutes')->with(100)->once()->andReturnSelf()
- ->shouldReceive('toDateTimeString')->withNoArgs()->once()->andReturn('00:00:00');
-
- $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf();
- $this->repository->shouldReceive('update')->with(123, [
- 'secret' => $secret,
- 'expires_at' => '00:00:00',
- ])->once()->andReturnNull();
-
- $response = $this->service->handle(123);
- $this->assertNotEmpty($response);
- $this->assertEquals($secret, $response);
- }
-}
diff --git a/tests/Unit/Services/DaemonKeys/RevokeMultipleDaemonKeysServiceTest.php b/tests/Unit/Services/DaemonKeys/RevokeMultipleDaemonKeysServiceTest.php
deleted file mode 100644
index a10753cc0..000000000
--- a/tests/Unit/Services/DaemonKeys/RevokeMultipleDaemonKeysServiceTest.php
+++ /dev/null
@@ -1,116 +0,0 @@
-daemonRepository = m::mock(ServerRepositoryInterface::class);
- $this->repository = m::mock(DaemonKeyRepositoryInterface::class);
- }
-
- /**
- * Test that keys can be successfully revoked.
- */
- public function testSuccessfulKeyRevocation()
- {
- $user = factory(User::class)->make();
- $node = factory(Node::class)->make();
- $key = factory(DaemonKey::class)->make(['user_id' => $user->id]);
- $key->setRelation('node', $node);
-
- $this->repository->shouldReceive('getKeysForRevocation')->with($user)->once()->andReturn(collect([$key]));
- $this->daemonRepository->shouldReceive('setNode')->with($node)->once()->andReturnSelf();
- $this->daemonRepository->shouldReceive('revokeAccessKey')->with([$key->secret])->once()->andReturn(new Response);
-
- $this->repository->shouldReceive('deleteKeys')->with([$key->id])->once()->andReturnNull();
-
- $this->getService()->handle($user);
- $this->assertTrue(true);
- }
-
- /**
- * Test that an exception thrown by a call to the daemon is handled.
- *
- * @expectedException \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
- */
- public function testExceptionThrownFromDaemonCallIsHandled()
- {
- $this->configureExceptionMock();
-
- $user = factory(User::class)->make();
- $node = factory(Node::class)->make();
- $key = factory(DaemonKey::class)->make(['user_id' => $user->id]);
- $key->setRelation('node', $node);
-
- $this->repository->shouldReceive('getKeysForRevocation')->with($user)->once()->andReturn(collect([$key]));
- $this->daemonRepository->shouldReceive('setNode->revokeAccessKey')->with([$key->secret])->once()->andThrow($this->getExceptionMock());
-
- $this->getService()->handle($user);
- }
-
- /**
- * Test that the behavior for handling exceptions that should not be thrown
- * immediately is working correctly and adds them to the array.
- */
- public function testIgnoredExceptionsAreHandledProperly()
- {
- $this->configureExceptionMock();
-
- $user = factory(User::class)->make();
- $node = factory(Node::class)->make();
- $key = factory(DaemonKey::class)->make(['user_id' => $user->id]);
- $key->setRelation('node', $node);
-
- $this->repository->shouldReceive('getKeysForRevocation')->with($user)->once()->andReturn(collect([$key]));
- $this->daemonRepository->shouldReceive('setNode->revokeAccessKey')->with([$key->secret])->once()->andThrow($this->getExceptionMock());
-
- $this->repository->shouldReceive('deleteKeys')->with([$key->id])->once()->andReturnNull();
-
- $service = $this->getService();
- $service->handle($user, true);
- $this->assertNotEmpty($service->getExceptions());
- $this->assertArrayHasKey($node->id, $service->getExceptions());
- $this->assertSame(array_get($service->getExceptions(), $node->id), $this->getExceptionMock());
- $this->assertTrue(true);
- }
-
- /**
- * Return an instance of the service for testing.
- *
- * @return \Pterodactyl\Services\DaemonKeys\RevokeMultipleDaemonKeysService
- */
- private function getService(): RevokeMultipleDaemonKeysService
- {
- return new RevokeMultipleDaemonKeysService($this->repository, $this->daemonRepository);
- }
-}
diff --git a/tests/Unit/Services/Databases/DatabasePasswordServiceTest.php b/tests/Unit/Services/Databases/DatabasePasswordServiceTest.php
index 25bfee164..226723e9d 100644
--- a/tests/Unit/Services/Databases/DatabasePasswordServiceTest.php
+++ b/tests/Unit/Services/Databases/DatabasePasswordServiceTest.php
@@ -51,13 +51,14 @@ class DatabasePasswordServiceTest extends TestCase
*/
public function testPasswordIsChanged()
{
- $model = factory(Database::class)->make();
+ /** @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->shouldReceive('set')->with('dynamic', $model->database_host_id)->once()->andReturnNull();
+ $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);
@@ -67,13 +68,13 @@ class DatabasePasswordServiceTest extends TestCase
return true;
}))->andReturn('enc123');
- $this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf();
- $this->repository->shouldReceive('update')->with($model->id, ['password' => 'enc123'])->once()->andReturn(true);
+ $this->repository->expects('withoutFreshModel')->withNoArgs()->andReturnSelf();
+ $this->repository->expects('update')->with($model->id, ['password' => 'enc123'])->andReturn(true);
- $this->repository->shouldReceive('dropUser')->with($model->username, $model->remote)->once()->andReturn(true);
- $this->repository->shouldReceive('createUser')->with($model->username, $model->remote, m::any())->once()->andReturn(true);
- $this->repository->shouldReceive('assignUserToDatabase')->with($model->database, $model->username, $model->remote)->once()->andReturn(true);
- $this->repository->shouldReceive('flush')->withNoArgs()->once()->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);
diff --git a/tests/Unit/Services/Databases/DeployServerDatabaseServiceTest.php b/tests/Unit/Services/Databases/DeployServerDatabaseServiceTest.php
index b709417d2..fc86cdcfc 100644
--- a/tests/Unit/Services/Databases/DeployServerDatabaseServiceTest.php
+++ b/tests/Unit/Services/Databases/DeployServerDatabaseServiceTest.php
@@ -10,6 +10,7 @@ use Pterodactyl\Services\Databases\DatabaseManagementService;
use Pterodactyl\Services\Databases\DeployServerDatabaseService;
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface;
+use Pterodactyl\Exceptions\Service\Database\NoSuitableDatabaseHostException;
class DeployServerDatabaseServiceTest extends TestCase
{
@@ -51,16 +52,9 @@ class DeployServerDatabaseServiceTest extends TestCase
*/
public function testNonRandomFoundHost($limit, $count)
{
- config()->set('pterodactyl.client_features.databases.allow_random', false);
-
$server = factory(Server::class)->make(['database_limit' => $limit]);
$model = factory(Database::class)->make();
- $this->repository->shouldReceive('findCountWhere')
- ->once()
- ->with([['server_id', '=', $server->id]])
- ->andReturn($count);
-
$this->databaseHostRepository->shouldReceive('setColumns->findWhere')
->once()
->with([['node_id', '=', $server->node_id]])
@@ -68,7 +62,7 @@ class DeployServerDatabaseServiceTest extends TestCase
$this->managementService->shouldReceive('create')
->once()
- ->with($server->id, [
+ ->with($server, [
'database_host_id' => $model->id,
'database' => 'testdb',
'remote' => null,
@@ -83,25 +77,20 @@ class DeployServerDatabaseServiceTest extends TestCase
/**
* Test that an exception is thrown if in non-random mode and no host is found.
- *
- * @expectedException \Pterodactyl\Exceptions\Service\Database\NoSuitableDatabaseHostException
*/
public function testNonRandomNoHost()
{
- config()->set('pterodactyl.client_features.databases.allow_random', false);
+ $this->expectException(NoSuitableDatabaseHostException::class);
$server = factory(Server::class)->make(['database_limit' => 1]);
- $this->repository->shouldReceive('findCountWhere')
- ->once()
- ->with([['server_id', '=', $server->id]])
- ->andReturn(0);
-
$this->databaseHostRepository->shouldReceive('setColumns->findWhere')
->once()
->with([['node_id', '=', $server->node_id]])
->andReturn(collect());
+ $this->databaseHostRepository->expects('setColumns->all')->withNoArgs()->andReturn(collect());
+
$this->getService()->handle($server, []);
}
@@ -113,11 +102,6 @@ class DeployServerDatabaseServiceTest extends TestCase
$server = factory(Server::class)->make(['database_limit' => 1]);
$model = factory(Database::class)->make();
- $this->repository->shouldReceive('findCountWhere')
- ->once()
- ->with([['server_id', '=', $server->id]])
- ->andReturn(0);
-
$this->databaseHostRepository->shouldReceive('setColumns->findWhere')
->once()
->with([['node_id', '=', $server->node_id]])
@@ -129,7 +113,7 @@ class DeployServerDatabaseServiceTest extends TestCase
$this->managementService->shouldReceive('create')
->once()
- ->with($server->id, [
+ ->with($server, [
'database_host_id' => $model->id,
'database' => 'testdb',
'remote' => null,
@@ -144,60 +128,22 @@ class DeployServerDatabaseServiceTest extends TestCase
/**
* Test that an exception is thrown when no host is found and random is allowed.
- *
- * @expectedException \Pterodactyl\Exceptions\Service\Database\NoSuitableDatabaseHostException
*/
public function testRandomNoHost()
{
+ $this->expectException(NoSuitableDatabaseHostException::class);
+
$server = factory(Server::class)->make(['database_limit' => 1]);
- $this->repository->shouldReceive('findCountWhere')
- ->once()
- ->with([['server_id', '=', $server->id]])
- ->andReturn(0);
-
- $this->databaseHostRepository->shouldReceive('setColumns->findWhere')
- ->once()
+ $this->databaseHostRepository->expects('setColumns->findWhere')
->with([['node_id', '=', $server->node_id]])
->andReturn(collect());
- $this->databaseHostRepository->shouldReceive('setColumns->all')
- ->once()
- ->andReturn(collect());
+ $this->databaseHostRepository->expects('setColumns->all')->withNoArgs()->andReturn(collect());
$this->getService()->handle($server, []);
}
- /**
- * Test that a server over the database limit throws an exception.
- *
- * @dataProvider databaseExceedingLimitDataProvider
- * @expectedException \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException
- */
- public function testServerOverDatabaseLimit($limit, $count)
- {
- $server = factory(Server::class)->make(['database_limit' => $limit]);
-
- $this->repository->shouldReceive('findCountWhere')
- ->once()
- ->with([['server_id', '=', $server->id]])
- ->andReturn($count);
-
- $this->getService()->handle($server, []);
- }
-
- /**
- * Test that an exception is thrown if the feature is not enabled.
- *
- * @expectedException \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException
- */
- public function testFeatureNotEnabled()
- {
- config()->set('pterodactyl.client_features.databases.enabled', false);
-
- $this->getService()->handle(factory(Server::class)->make(), []);
- }
-
/**
* Provide limits and current database counts for testing.
*
diff --git a/tests/Unit/Services/Eggs/EggConfigurationServiceTest.php b/tests/Unit/Services/Eggs/EggConfigurationServiceTest.php
deleted file mode 100644
index f6b1bebb9..000000000
--- a/tests/Unit/Services/Eggs/EggConfigurationServiceTest.php
+++ /dev/null
@@ -1,90 +0,0 @@
-repository = m::mock(EggRepositoryInterface::class);
-
- $this->service = new EggConfigurationService($this->repository);
- }
-
- /**
- * Test that the correct array is returned.
- */
- public function testCorrectArrayIsReturned()
- {
- $egg = factory(Egg::class)->make([
- 'config_startup' => '{"test": "start"}',
- 'config_stop' => 'test',
- 'config_files' => '{"test": "file"}',
- 'config_logs' => '{"test": "logs"}',
- ]);
-
- $response = $this->service->handle($egg);
- $this->assertNotEmpty($response);
- $this->assertTrue(is_array($response), 'Assert response is an array.');
- $this->assertArrayHasKey('startup', $response);
- $this->assertArrayHasKey('stop', $response);
- $this->assertArrayHasKey('configs', $response);
- $this->assertArrayHasKey('log', $response);
- $this->assertArrayHasKey('query', $response);
- $this->assertEquals('start', object_get($response['startup'], 'test'));
- $this->assertEquals('test', 'test');
- $this->assertEquals('file', object_get($response['configs'], 'test'));
- $this->assertEquals('logs', object_get($response['log'], 'test'));
- $this->assertEquals('none', $response['query']);
- }
-
- /**
- * Test that an integer referencing a model can be passed in place of the model.
- */
- public function testFunctionHandlesIntegerPassedInPlaceOfModel()
- {
- $egg = factory(Egg::class)->make([
- 'config_startup' => '{"test": "start"}',
- 'config_stop' => 'test',
- 'config_files' => '{"test": "file"}',
- 'config_logs' => '{"test": "logs"}',
- ]);
-
- $this->repository->shouldReceive('getWithCopyAttributes')->with($egg->id)->once()->andReturn($egg);
-
- $response = $this->service->handle($egg->id);
- $this->assertNotEmpty($response);
- $this->assertTrue(is_array($response), 'Assert response is an array.');
- $this->assertArrayHasKey('startup', $response);
- $this->assertArrayHasKey('stop', $response);
- $this->assertArrayHasKey('configs', $response);
- $this->assertArrayHasKey('log', $response);
- $this->assertArrayHasKey('query', $response);
- $this->assertEquals('start', object_get($response['startup'], 'test'));
- $this->assertEquals('test', 'test');
- $this->assertEquals('file', object_get($response['configs'], 'test'));
- $this->assertEquals('logs', object_get($response['log'], 'test'));
- $this->assertEquals('none', $response['query']);
- }
-}
diff --git a/tests/Unit/Services/Eggs/Variables/VariableCreationServiceTest.php b/tests/Unit/Services/Eggs/Variables/VariableCreationServiceTest.php
index 34c7bdbdd..bbac6009d 100644
--- a/tests/Unit/Services/Eggs/Variables/VariableCreationServiceTest.php
+++ b/tests/Unit/Services/Eggs/Variables/VariableCreationServiceTest.php
@@ -9,6 +9,8 @@ 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
{
@@ -91,10 +93,11 @@ class VariableCreationServiceTest extends TestCase
* @param string $variable
*
* @dataProvider reservedNamesProvider
- * @expectedException \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException
*/
public function testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames(string $variable)
{
+ $this->expectException(ReservedVariableNameException::class);
+
$this->getService()->handle(1, ['env_variable' => $variable]);
}
@@ -114,12 +117,12 @@ class VariableCreationServiceTest extends TestCase
/**
* Test that validation errors due to invalid rules are caught and handled properly.
- *
- * @expectedException \Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException
- * @expectedExceptionMessage The validation rule "hodor_door" is not a valid rule for this application.
*/
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()
@@ -135,12 +138,12 @@ class VariableCreationServiceTest extends TestCase
/**
* Test that an exception not stemming from a bad rule is not caught.
- *
- * @expectedException \BadMethodCallException
- * @expectedExceptionMessage Received something, but no expectations were specified.
*/
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()
diff --git a/tests/Unit/Services/Eggs/Variables/VariableUpdateServiceTest.php b/tests/Unit/Services/Eggs/Variables/VariableUpdateServiceTest.php
index 82dd00c0b..a812da274 100644
--- a/tests/Unit/Services/Eggs/Variables/VariableUpdateServiceTest.php
+++ b/tests/Unit/Services/Eggs/Variables/VariableUpdateServiceTest.php
@@ -11,6 +11,8 @@ 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
{
@@ -159,21 +161,22 @@ class VariableUpdateServiceTest extends TestCase
* Test that all of the reserved variables defined in the model trigger an exception.
*
* @dataProvider reservedNamesProvider
- * @expectedException \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException
*/
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.
- *
- * @expectedException \Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException
- * @expectedExceptionMessage The validation rule "hodor_door" is not a valid rule for this application.
*/
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);
@@ -191,12 +194,12 @@ class VariableUpdateServiceTest extends TestCase
/**
* Test that an exception not stemming from a bad rule is not caught.
- *
- * @expectedException \BadMethodCallException
- * @expectedExceptionMessage Received something, but no expectations were specified.
*/
public function testExceptionNotCausedByBadRuleIsNotCaught()
{
+ $this->expectException(BadMethodCallException::class);
+ $this->expectExceptionMessage('Received something, but no expectations were specified.');
+
$data = ['rules' => 'string'];
$this->validator->shouldReceive('make')->once()
diff --git a/tests/Unit/Services/Helpers/SoftwareVersionServiceTest.php b/tests/Unit/Services/Helpers/SoftwareVersionServiceTest.php
deleted file mode 100644
index d0ada1b4a..000000000
--- a/tests/Unit/Services/Helpers/SoftwareVersionServiceTest.php
+++ /dev/null
@@ -1,168 +0,0 @@
-.
- *
- * This software is licensed under the terms of the MIT license.
- * https://opensource.org/licenses/MIT
- */
-
-namespace Tests\Unit\Services\Helpers;
-
-use Closure;
-use Mockery as m;
-use Tests\TestCase;
-use GuzzleHttp\Client;
-use Pterodactyl\Services\Helpers\SoftwareVersionService;
-use Illuminate\Contracts\Cache\Repository as CacheRepository;
-use Illuminate\Contracts\Config\Repository as ConfigRepository;
-
-class SoftwareVersionServiceTest extends TestCase
-{
- /**
- * @var \Illuminate\Contracts\Cache\Repository
- */
- protected $cache;
-
- /**
- * @var \GuzzleHttp\Client
- */
- protected $client;
-
- /**
- * @var \Illuminate\Contracts\Config\Repository
- */
- protected $config;
-
- /**
- * @var object
- */
- protected static $response = [
- 'panel' => '0.2.0',
- 'daemon' => '0.1.0',
- 'discord' => 'https://pterodactyl.io/discord',
- ];
-
- /**
- * @var \Pterodactyl\Services\Helpers\SoftwareVersionService
- */
- protected $service;
-
- /**
- * Setup tests.
- */
- public function setUp(): void
- {
- parent::setUp();
-
- self::$response = (object) self::$response;
-
- $this->cache = m::mock(CacheRepository::class);
- $this->client = m::mock(Client::class);
- $this->config = m::mock(ConfigRepository::class);
-
- $this->config->shouldReceive('get')->with('pterodactyl.cdn.cache_time')->once()->andReturn(60);
-
- $this->cache->shouldReceive('remember')->with(SoftwareVersionService::VERSION_CACHE_KEY, 60, Closure::class)->once()->andReturnNull();
-
- $this->service = m::mock(SoftwareVersionService::class, [$this->cache, $this->client, $this->config])->makePartial();
- }
-
- /**
- * Test that the panel version is returned.
- */
- public function testPanelVersionIsReturned()
- {
- $this->cache->shouldReceive('get')->with(SoftwareVersionService::VERSION_CACHE_KEY)->once()->andReturn(self::$response);
- $this->assertEquals(self::$response->panel, $this->service->getPanel());
- }
-
- /**
- * Test that the panel version is returned as error.
- */
- public function testPanelVersionIsReturnedAsErrorIfNoKeyIsFound()
- {
- $this->cache->shouldReceive('get')->with(SoftwareVersionService::VERSION_CACHE_KEY)->once()->andReturn((object) []);
- $this->assertEquals('error', $this->service->getPanel());
- }
-
- /**
- * Test that the daemon version is returned.
- */
- public function testDaemonVersionIsReturned()
- {
- $this->cache->shouldReceive('get')->with(SoftwareVersionService::VERSION_CACHE_KEY)->once()->andReturn(self::$response);
- $this->assertEquals(self::$response->daemon, $this->service->getDaemon());
- }
-
- /**
- * Test that the daemon version is returned as an error.
- */
- public function testDaemonVersionIsReturnedAsErrorIfNoKeyIsFound()
- {
- $this->cache->shouldReceive('get')->with(SoftwareVersionService::VERSION_CACHE_KEY)->once()->andReturn((object) []);
- $this->assertEquals('error', $this->service->getDaemon());
- }
-
- /**
- * Test that the discord URL is returned.
- */
- public function testDiscordUrlIsReturned()
- {
- $this->cache->shouldReceive('get')->with(SoftwareVersionService::VERSION_CACHE_KEY)->once()->andReturn(self::$response);
- $this->assertEquals(self::$response->discord, $this->service->getDiscord());
- }
-
- /**
- * Test that the correct boolean value is returned by the helper for each version passed.
- *
- * @dataProvider panelVersionProvider
- */
- public function testCorrectBooleanValueIsReturnedWhenCheckingPanelVersion($version, $response)
- {
- $this->config->shouldReceive('get')->with('app.version')->andReturn($version);
- $this->service->shouldReceive('getPanel')->withNoArgs()->andReturn(self::$response->panel);
-
- $this->assertEquals($response, $this->service->isLatestPanel());
- }
-
- /**
- * Test that the correct boolean value is returned.
- *
- * @dataProvider daemonVersionProvider
- */
- public function testCorrectBooleanValueIsReturnedWhenCheckingDaemonVersion($version, $response)
- {
- $this->service->shouldReceive('getDaemon')->withNoArgs()->andReturn(self::$response->daemon);
-
- $this->assertEquals($response, $this->service->isLatestDaemon($version));
- }
-
- /**
- * Provide data for testing boolean response on panel version.
- *
- * @return array
- */
- public function panelVersionProvider()
- {
- return [
- [self::$response['panel'], true],
- ['0.0.1', false],
- ['canary', true],
- ];
- }
-
- /**
- * Provide data for testing boolean response for daemon version.
- *
- * @return array
- */
- public function daemonVersionProvider()
- {
- return [
- [self::$response['daemon'], true],
- ['0.0.1', false],
- ['0.0.0-canary', true],
- ];
- }
-}
diff --git a/tests/Unit/Services/Nodes/NodeCreationServiceTest.php b/tests/Unit/Services/Nodes/NodeCreationServiceTest.php
index bf7cb05ed..561a14acc 100644
--- a/tests/Unit/Services/Nodes/NodeCreationServiceTest.php
+++ b/tests/Unit/Services/Nodes/NodeCreationServiceTest.php
@@ -1,17 +1,14 @@
.
- *
- * This software is licensed under the terms of the MIT license.
- * https://opensource.org/licenses/MIT
- */
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;
@@ -20,14 +17,14 @@ class NodeCreationServiceTest extends TestCase
use PHPMock;
/**
- * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface
+ * @var \Mockery\MockInterface
*/
- protected $repository;
+ private $repository;
/**
- * @var \Pterodactyl\Services\Nodes\NodeCreationService
+ * @var \Mockery\MockInterface
*/
- protected $service;
+ private $encrypter;
/**
* Setup tests.
@@ -36,9 +33,15 @@ class NodeCreationServiceTest extends TestCase
{
parent::setUp();
- $this->repository = m::mock(NodeRepositoryInterface::class);
+ /* @noinspection PhpParamsInspection */
+ Uuid::setFactory(
+ m::mock(UuidFactory::class . '[uuid4]', [
+ 'uuid4' => Uuid::fromString('00000000-0000-0000-0000-000000000000'),
+ ])
+ );
- $this->service = new NodeCreationService($this->repository);
+ $this->repository = m::mock(NodeRepositoryInterface::class);
+ $this->encrypter = m::mock(Encrypter::class);
}
/**
@@ -46,14 +49,31 @@ class NodeCreationServiceTest extends TestCase
*/
public function testNodeIsCreatedAndDaemonSecretIsGenerated()
{
- $this->getFunctionMock('\\Pterodactyl\\Services\\Nodes', 'str_random')
- ->expects($this->once())->willReturn('random_string');
+ /** @var \Pterodactyl\Models\Node $node */
+ $node = factory(Node::class)->make();
- $this->repository->shouldReceive('create')->with([
- 'name' => 'NodeName',
- 'daemonSecret' => 'random_string',
- ])->once()->andReturnNull();
+ $this->encrypter->expects('encrypt')->with(m::on(function ($value) {
+ return strlen($value) === Node::DAEMON_TOKEN_LENGTH;
+ }))->andReturns('encrypted_value');
- $this->assertNull($this->service->handle(['name' => 'NodeName']));
+ $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);
}
}
diff --git a/tests/Unit/Services/Nodes/NodeDeletionServiceTest.php b/tests/Unit/Services/Nodes/NodeDeletionServiceTest.php
index eb2f05f69..88ebaaaf5 100644
--- a/tests/Unit/Services/Nodes/NodeDeletionServiceTest.php
+++ b/tests/Unit/Services/Nodes/NodeDeletionServiceTest.php
@@ -12,6 +12,7 @@ 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;
@@ -71,11 +72,11 @@ class NodeDeletionServiceTest extends TestCase
/**
* Test that an exception is thrown if servers are attached to the node.
- *
- * @expectedException \Pterodactyl\Exceptions\DisplayException
*/
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();
diff --git a/tests/Unit/Services/Nodes/NodeUpdateServiceTest.php b/tests/Unit/Services/Nodes/NodeUpdateServiceTest.php
index c8596b66b..c8138185d 100644
--- a/tests/Unit/Services/Nodes/NodeUpdateServiceTest.php
+++ b/tests/Unit/Services/Nodes/NodeUpdateServiceTest.php
@@ -2,34 +2,44 @@
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 GuzzleHttp\Psr7\Response;
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\Contracts\Repository\NodeRepositoryInterface;
-use Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface;
+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 \Illuminate\Database\ConnectionInterface|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
private $connection;
/**
- * @var \Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
- private $configRepository;
+ private $configurationRepository;
/**
- * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface|\Mockery\Mock
+ * @var \Mockery\MockInterface
+ */
+ private $encrypter;
+
+ /**
+ * @var \Mockery\MockInterface
*/
private $repository;
@@ -41,8 +51,9 @@ class NodeUpdateServiceTest extends TestCase
parent::setUp();
$this->connection = m::mock(ConnectionInterface::class);
- $this->configRepository = m::mock(ConfigurationRepositoryInterface::class);
- $this->repository = m::mock(NodeRepositoryInterface::class);
+ $this->encrypter = m::mock(Encrypter::class);
+ $this->configurationRepository = m::mock(DaemonConfigurationRepository::class);
+ $this->repository = m::mock(NodeRepository::class);
}
/**
@@ -50,36 +61,59 @@ class NodeUpdateServiceTest extends TestCase
*/
public function testNodeIsUpdatedAndDaemonSecretIsReset()
{
- $model = factory(Node::class)->make();
- $updatedModel = factory(Node::class)->make([
- 'name' => 'New Name',
- 'daemonSecret' => 'abcd1234',
+ /** @var \Pterodactyl\Models\Node $model */
+ $model = factory(Node::class)->make([
+ 'fqdn' => 'https://example.com',
]);
- $this->getFunctionMock('\\Pterodactyl\\Services\\Nodes', 'str_random')
- ->expects($this->once())->willReturn($updatedModel->daemonSecret);
+ /** @var \Pterodactyl\Models\Node $updatedModel */
+ $updatedModel = factory(Node::class)->make([
+ 'name' => 'New Name',
+ 'fqdn' => 'https://example2.com',
+ ]);
- $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
- $this->repository->shouldReceive('update')->with($model->id, [
+ $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,
- 'daemonSecret' => $updatedModel->daemonSecret,
- ])->andReturn($model);
-
- $cloned = $updatedModel->replicate(['daemonSecret']);
- $cloned->daemonSecret = $model->daemonSecret;
-
- $this->configRepository->shouldReceive('setNode')->with(m::on(function ($model) use ($updatedModel) {
- return $model->daemonSecret !== $updatedModel->daemonSecret;
- }))->once()->andReturnSelf();
-
- $this->configRepository->shouldReceive('update')->with([
- 'keys' => ['abcd1234'],
- ])->once()->andReturn(new Response);
-
- $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
-
- $response = $this->getService()->handle($model, ['name' => $updatedModel->name], true);
- $this->assertInstanceOf(Node::class, $response);
+ ], true);
}
/**
@@ -87,56 +121,115 @@ class NodeUpdateServiceTest extends TestCase
*/
public function testNodeIsUpdatedAndDaemonSecretIsNotChanged()
{
- $model = factory(Node::class)->make();
+ /** @var \Pterodactyl\Models\Node $model */
+ $model = factory(Node::class)->make(['fqdn' => 'https://example.com']);
- $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
- $this->repository->shouldReceive('update')->with($model->id, [
- 'name' => 'NewName',
- ])->andReturn($model);
+ /** @var \Pterodactyl\Models\Node $updatedModel */
+ $updatedModel = factory(Node::class)->make(['name' => 'New Name', 'fqdn' => $model->fqdn]);
- $this->configRepository->shouldReceive('setNode')->with($model)->once()->andReturnSelf()
- ->shouldReceive('update')->withNoArgs()->once()->andReturn(new Response);
- $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
+ $this->connection->expects('transaction')->with(m::on(function ($closure) use ($updatedModel) {
+ $response = $closure();
- $response = $this->getService()->handle($model, ['name' => 'NewName']);
- $this->assertInstanceOf(Node::class, $response);
+ $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.
- *
- * @expectedException \Pterodactyl\Exceptions\Service\Node\ConfigurationNotPersistedException
*/
public function testExceptionRelatedToConnection()
{
- $this->configureExceptionMock(ConnectException::class);
- $model = factory(Node::class)->make();
+ $this->configureExceptionMock(DaemonConnectionException::class);
+ $this->expectException(ConfigurationNotPersistedException::class);
- $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
- $this->repository->shouldReceive('update')->andReturn($model);
+ /** @var \Pterodactyl\Models\Node $model */
+ $model = factory(Node::class)->make(['fqdn' => 'https://example.com']);
- $this->configRepository->shouldReceive('setNode->update')->once()->andThrow($this->getExceptionMock());
- $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
+ /** @var \Pterodactyl\Models\Node $updatedModel */
+ $updatedModel = factory(Node::class)->make(['name' => 'New Name', 'fqdn' => $model->fqdn]);
- $this->getService()->handle($model, ['name' => 'NewName']);
+ $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.
- *
- * @expectedException \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function testExceptionNotRelatedToConnection()
{
- $this->configureExceptionMock();
- $model = factory(Node::class)->make();
+ /** @var \Pterodactyl\Models\Node $model */
+ $model = factory(Node::class)->make(['fqdn' => 'https://example.com']);
- $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
- $this->repository->shouldReceive('update')->andReturn($model);
+ /** @var \Pterodactyl\Models\Node $updatedModel */
+ $updatedModel = factory(Node::class)->make(['name' => 'New Name', 'fqdn' => $model->fqdn]);
- $this->configRepository->shouldReceive('setNode->update')->once()->andThrow($this->getExceptionMock());
+ $this->connection->expects('transaction')->with(m::on(function ($closure) use ($updatedModel) {
+ try {
+ $closure();
+ } catch (Exception $exception) {
+ $this->assertInstanceOf(DaemonConnectionException::class, $exception);
+ $this->assertSame(
+ 'There was an exception while attempting to communicate with the daemon resulting in a HTTP/E_CONN_REFUSED response code. This exception has been logged.',
+ $exception->getMessage()
+ );
- $this->getService()->handle($model, ['name' => 'NewName']);
+ return true;
+ }
+
+ return false;
+ }));
+
+ $this->repository->expects('withFreshModel->update')->andReturns($updatedModel);
+ $this->configurationRepository->expects('setNode->update')->andThrow(
+ new DaemonConnectionException(
+ new TransferException('', 500, new Exception)
+ )
+ );
+
+ $this->getService()->handle($model, ['name' => $updatedModel->name]);
}
/**
@@ -146,6 +239,8 @@ class NodeUpdateServiceTest extends TestCase
*/
private function getService(): NodeUpdateService
{
- return new NodeUpdateService($this->connection, $this->configRepository, $this->repository);
+ return new NodeUpdateService(
+ $this->connection, $this->encrypter, $this->configurationRepository, $this->repository
+ );
}
}
diff --git a/tests/Unit/Services/Packs/ExportPackServiceTest.php b/tests/Unit/Services/Packs/ExportPackServiceTest.php
index 031e1fb39..936b7b06d 100644
--- a/tests/Unit/Services/Packs/ExportPackServiceTest.php
+++ b/tests/Unit/Services/Packs/ExportPackServiceTest.php
@@ -17,6 +17,7 @@ use Pterodactyl\Models\Pack;
use Illuminate\Contracts\Filesystem\Factory;
use Pterodactyl\Services\Packs\ExportPackService;
use Pterodactyl\Contracts\Repository\PackRepositoryInterface;
+use Pterodactyl\Exceptions\Service\Pack\ZipArchiveCreationException;
class ExportPackServiceTest extends TestCase
{
@@ -132,11 +133,11 @@ class ExportPackServiceTest extends TestCase
/**
* Test that an exception is thrown when a ZipArchive cannot be created.
- *
- * @expectedException \Pterodactyl\Exceptions\Service\Pack\ZipArchiveCreationException
*/
public function testExceptionIsThrownIfZipArchiveCannotBeCreated()
{
+ $this->expectException(ZipArchiveCreationException::class);
+
$this->setupTestData();
$this->getFunctionMock('\\Pterodactyl\\Services\\Packs', 'tempnam')
diff --git a/tests/Unit/Services/Servers/ReinstallServerServiceTest.php b/tests/Unit/Services/Servers/ReinstallServerServiceTest.php
index 8bd95f0b8..22cc35199 100644
--- a/tests/Unit/Services/Servers/ReinstallServerServiceTest.php
+++ b/tests/Unit/Services/Servers/ReinstallServerServiceTest.php
@@ -9,48 +9,30 @@
namespace Tests\Unit\Services\Servers;
-use Exception;
use Mockery as m;
use Tests\TestCase;
-use GuzzleHttp\Psr7\Response;
use Pterodactyl\Models\Server;
-use GuzzleHttp\Exception\RequestException;
use Illuminate\Database\ConnectionInterface;
+use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Services\Servers\ReinstallServerService;
-use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
-use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
+use Pterodactyl\Repositories\Wings\DaemonServerRepository;
class ReinstallServerServiceTest extends TestCase
{
/**
- * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
+ * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository
*/
- protected $daemonServerRepository;
+ private $daemonServerRepository;
/**
* @var \Illuminate\Database\ConnectionInterface
*/
- protected $database;
-
- /**
- * @var \GuzzleHttp\Exception\RequestException
- */
- protected $exception;
+ private $connection;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
- protected $repository;
-
- /**
- * @var \Pterodactyl\Models\Server
- */
- protected $server;
-
- /**
- * @var \Pterodactyl\Services\Servers\ReinstallServerService
- */
- protected $service;
+ private $repository;
/**
* Setup tests.
@@ -59,18 +41,9 @@ class ReinstallServerServiceTest extends TestCase
{
parent::setUp();
- $this->daemonServerRepository = m::mock(DaemonServerRepositoryInterface::class);
- $this->database = m::mock(ConnectionInterface::class);
- $this->exception = m::mock(RequestException::class)->makePartial();
- $this->repository = m::mock(ServerRepositoryInterface::class);
-
- $this->server = factory(Server::class)->make(['node_id' => 1]);
-
- $this->service = new ReinstallServerService(
- $this->database,
- $this->daemonServerRepository,
- $this->repository
- );
+ $this->repository = m::mock(ServerRepository::class);
+ $this->connection = m::mock(ConnectionInterface::class);
+ $this->daemonServerRepository = m::mock(DaemonServerRepository::class);
}
/**
@@ -78,70 +51,32 @@ class ReinstallServerServiceTest extends TestCase
*/
public function testServerShouldBeReinstalledWhenModelIsPassed()
{
- $this->repository->shouldNotReceive('find');
+ /** @var \Pterodactyl\Models\Server $server */
+ $server = factory(Server::class)->make(['id' => 123]);
+ $updated = clone $server;
+ $updated->installed = Server::STATUS_INSTALLING;
- $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
- $this->repository->shouldReceive('withoutFreshModel->update')->with($this->server->id, [
- 'installed' => 0,
- ], true, true)->once()->andReturnNull();
+ $this->connection->expects('transaction')->with(m::on(function ($closure) use ($updated) {
+ return $closure() instanceof Server;
+ }))->andReturn($updated);
- $this->daemonServerRepository->shouldReceive('setServer')->with($this->server)->once()->andReturnSelf()
- ->shouldReceive('reinstall')->withNoArgs()->once()->andReturn(new Response);
- $this->database->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
+ $this->repository->expects('update')->with($server->id, [
+ 'installed' => Server::STATUS_INSTALLING,
+ ])->andReturns($updated);
- $this->service->reinstall($this->server);
+ $this->daemonServerRepository->expects('setServer')->with($server)->andReturnSelf();
+ $this->daemonServerRepository->expects('reinstall')->withNoArgs();
+
+ $this->assertSame($updated, $this->getService()->reinstall($server));
}
/**
- * Test that a server is reinstalled when the ID of the server is passed to the function.
+ * @return \Pterodactyl\Services\Servers\ReinstallServerService
*/
- public function testServerShouldBeReinstalledWhenServerIdIsPassed()
+ private function getService()
{
- $this->repository->shouldReceive('find')->with($this->server->id)->once()->andReturn($this->server);
-
- $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
- $this->repository->shouldReceive('withoutFreshModel->update')->with($this->server->id, [
- 'installed' => 0,
- ], true, true)->once()->andReturnNull();
-
- $this->daemonServerRepository->shouldReceive('setServer')->with($this->server)->once()->andReturnSelf()
- ->shouldReceive('reinstall')->withNoArgs()->once()->andReturn(new Response);
- $this->database->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
-
- $this->service->reinstall($this->server->id);
- }
-
- /**
- * Test that an exception thrown by guzzle is rendered as a displayable exception.
- *
- * @expectedException \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
- */
- public function testExceptionThrownByGuzzleShouldBeReRenderedAsDisplayable()
- {
- $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
- $this->repository->shouldReceive('withoutFreshModel->update')->with($this->server->id, [
- 'installed' => 0,
- ], true, true)->once()->andReturnNull();
-
- $this->daemonServerRepository->shouldReceive('setServer')->with($this->server)->once()->andThrow($this->exception);
-
- $this->service->reinstall($this->server);
- }
-
- /**
- * Test that an exception thrown by something other than guzzle is not transformed to a displayable.
- *
- * @expectedException \Exception
- */
- public function testExceptionNotThrownByGuzzleShouldNotBeTransformedToDisplayable()
- {
- $this->database->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
- $this->repository->shouldReceive('withoutFreshModel->update')->with($this->server->id, [
- 'installed' => 0,
- ], true, true)->once()->andReturnNull();
-
- $this->daemonServerRepository->shouldReceive('setServer')->with($this->server)->once()->andThrow(new Exception());
-
- $this->service->reinstall($this->server);
+ return new ReinstallServerService(
+ $this->connection, $this->daemonServerRepository, $this->repository
+ );
}
}
diff --git a/tests/Unit/Services/Servers/ServerConfigurationStructureServiceTest.php b/tests/Unit/Services/Servers/ServerConfigurationStructureServiceTest.php
index aa63cfa4b..e6c301b92 100644
--- a/tests/Unit/Services/Servers/ServerConfigurationStructureServiceTest.php
+++ b/tests/Unit/Services/Servers/ServerConfigurationStructureServiceTest.php
@@ -40,53 +40,62 @@ class ServerConfigurationStructureServiceTest extends TestCase
*/
public function testCorrectStructureIsReturned()
{
+ /** @var \Pterodactyl\Models\Server $model */
$model = factory(Server::class)->make();
$model->setRelation('pack', null);
$model->setRelation('allocation', factory(Allocation::class)->make());
$model->setRelation('allocations', collect(factory(Allocation::class)->times(2)->make()));
$model->setRelation('egg', factory(Egg::class)->make());
- $portListing = $model->allocations->groupBy('ip')->map(function ($item) {
- return $item->pluck('port');
- })->toArray();
-
- $this->repository->shouldReceive('getDataForCreation')->with($model)->once()->andReturn($model);
- $this->environment->shouldReceive('handle')->with($model)->once()->andReturn(['environment_array']);
+ $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('build', $response);
$this->assertArrayHasKey('service', $response);
- $this->assertArrayHasKey('rebuild', $response);
- $this->assertArrayHasKey('suspended', $response);
+ $this->assertArrayHasKey('container', $response);
+ $this->assertArrayHasKey('allocations', $response);
- $this->assertArraySubset([
+ $this->assertSame([
'default' => [
'ip' => $model->allocation->ip,
'port' => $model->allocation->port,
],
- ], $response['build'], true, 'Assert server default allocation is correct.');
- $this->assertArraySubset(['ports' => $portListing], $response['build'], true, 'Assert server ports are correct.');
- $this->assertArraySubset([
- 'env' => ['environment_array'],
- 'swap' => (int) $model->swap,
- 'io' => (int) $model->io,
- 'cpu' => (int) $model->cpu,
- 'disk' => (int) $model->disk,
- 'image' => $model->image,
- ], $response['build'], true, 'Assert server build data is correct.');
+ 'mappings' => $model->getAllocationMappings(),
+ ], $response['allocations']);
- $this->assertArraySubset([
+ $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([
'egg' => $model->egg->uuid,
'pack' => null,
'skip_scripts' => $model->skip_scripts,
], $response['service']);
- $this->assertFalse($response['rebuild']);
- $this->assertSame((int) $model->suspended, $response['suspended']);
+ $this->assertSame([
+ 'image' => $model->image,
+ 'oom_disabled' => $model->oom_disabled,
+ 'requires_rebuild' => false,
+ ], $response['container']);
+
+ $this->assertSame($model->uuid, $response['uuid']);
+ $this->assertSame((bool) $model->suspended, $response['suspended']);
+ $this->assertSame(['environment_array'], $response['environment']);
+ $this->assertSame($model->startup, $response['invocation']);
}
/**
diff --git a/tests/Unit/Services/Servers/ServerCreationServiceTest.php b/tests/Unit/Services/Servers/ServerCreationServiceTest.php
index 27c7892b6..4efdd926d 100644
--- a/tests/Unit/Services/Servers/ServerCreationServiceTest.php
+++ b/tests/Unit/Services/Servers/ServerCreationServiceTest.php
@@ -5,24 +5,27 @@ namespace Tests\Unit\Services\Servers;
use Mockery as m;
use Tests\TestCase;
use Pterodactyl\Models\Egg;
+use GuzzleHttp\Psr7\Request;
use Pterodactyl\Models\User;
use Tests\Traits\MocksUuids;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Allocation;
use Tests\Traits\MocksRequestException;
+use GuzzleHttp\Exception\ConnectException;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Models\Objects\DeploymentObject;
+use Pterodactyl\Repositories\Eloquent\EggRepository;
+use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Services\Servers\ServerCreationService;
+use Pterodactyl\Services\Servers\ServerDeletionService;
+use Pterodactyl\Repositories\Wings\DaemonServerRepository;
use Pterodactyl\Services\Servers\VariableValidatorService;
+use Pterodactyl\Repositories\Eloquent\AllocationRepository;
use Pterodactyl\Services\Deployment\FindViableNodesService;
-use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
-use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
-use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
+use Pterodactyl\Repositories\Eloquent\ServerVariableRepository;
use Pterodactyl\Services\Deployment\AllocationSelectionService;
-use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
+use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
use Pterodactyl\Services\Servers\ServerConfigurationStructureService;
-use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface;
-use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
/**
* @preserveGlobalState disabled
@@ -32,60 +35,60 @@ class ServerCreationServiceTest extends TestCase
use MocksRequestException, MocksUuids;
/**
- * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
private $allocationRepository;
/**
- * @var \Pterodactyl\Services\Deployment\AllocationSelectionService|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
private $allocationSelectionService;
/**
- * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
private $configurationStructureService;
/**
- * @var \Illuminate\Database\ConnectionInterface|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
private $connection;
/**
- * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
private $daemonServerRepository;
/**
- * @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
private $eggRepository;
/**
- * @var \Pterodactyl\Services\Deployment\FindViableNodesService|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
private $findViableNodesService;
/**
- * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
private $repository;
/**
- * @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
private $serverVariableRepository;
/**
- * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface|\Mockery\Mock
- */
- private $userRepository;
-
- /**
- * @var \Pterodactyl\Services\Servers\VariableValidatorService|\Mockery\Mock
+ * @var \Mockery\MockInterface
*/
private $validatorService;
+ /**
+ * @var \Mockery\MockInterface
+ */
+ private $serverDeletionService;
+
/**
* Setup tests.
*/
@@ -93,17 +96,17 @@ class ServerCreationServiceTest extends TestCase
{
parent::setUp();
- $this->allocationRepository = m::mock(AllocationRepositoryInterface::class);
+ $this->allocationRepository = m::mock(AllocationRepository::class);
$this->allocationSelectionService = m::mock(AllocationSelectionService::class);
$this->configurationStructureService = m::mock(ServerConfigurationStructureService::class);
$this->connection = m::mock(ConnectionInterface::class);
- $this->daemonServerRepository = m::mock(DaemonServerRepositoryInterface::class);
- $this->eggRepository = m::mock(EggRepositoryInterface::class);
$this->findViableNodesService = m::mock(FindViableNodesService::class);
- $this->repository = m::mock(ServerRepositoryInterface::class);
- $this->serverVariableRepository = m::mock(ServerVariableRepositoryInterface::class);
- $this->userRepository = m::mock(UserRepositoryInterface::class);
$this->validatorService = m::mock(VariableValidatorService::class);
+ $this->eggRepository = m::mock(EggRepository::class);
+ $this->repository = m::mock(ServerRepository::class);
+ $this->serverVariableRepository = m::mock(ServerVariableRepository::class);
+ $this->daemonServerRepository = m::mock(DaemonServerRepository::class);
+ $this->serverDeletionService = m::mock(ServerDeletionService::class);
}
/**
@@ -148,7 +151,7 @@ class ServerCreationServiceTest extends TestCase
$this->configurationStructureService->shouldReceive('handle')->with($model)->once()->andReturn(['test' => 'struct']);
$this->daemonServerRepository->shouldReceive('setServer')->with($model)->once()->andReturnSelf();
- $this->daemonServerRepository->shouldReceive('create')->with(['test' => 'struct'], ['start_on_completion' => false])->once();
+ $this->daemonServerRepository->shouldReceive('create')->with(['test' => 'struct'])->once();
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
$response = $this->getService()->handle($model->toArray());
@@ -250,12 +253,10 @@ class ServerCreationServiceTest extends TestCase
/**
* Test handling of node timeout or other daemon error.
- *
- * @expectedException \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function testExceptionShouldBeThrownIfTheRequestFails()
{
- $this->configureExceptionMock();
+ $this->expectException(DaemonConnectionException::class);
$model = factory(Server::class)->make([
'uuid' => $this->getKnownUuid(),
@@ -269,8 +270,16 @@ class ServerCreationServiceTest extends TestCase
$this->validatorService->shouldReceive('handle')->once()->andReturn(collect([]));
$this->configurationStructureService->shouldReceive('handle')->once()->andReturn([]);
- $this->daemonServerRepository->shouldReceive('setServer')->with($model)->once()->andThrow($this->getExceptionMock());
- $this->connection->shouldReceive('rollBack')->withNoArgs()->once()->andReturnNull();
+ $this->connection->expects('commit')->withNoArgs();
+
+ $this->daemonServerRepository->shouldReceive('setServer')->with($model)->once()->andThrow(
+ new DaemonConnectionException(
+ new ConnectException('', new Request('GET', 'test'))
+ )
+ );
+
+ $this->serverDeletionService->expects('withForce')->with(true)->andReturnSelf();
+ $this->serverDeletionService->expects('handle')->with($model);
$this->getService()->handle($model->toArray());
}
@@ -290,6 +299,7 @@ class ServerCreationServiceTest extends TestCase
$this->eggRepository,
$this->findViableNodesService,
$this->configurationStructureService,
+ $this->serverDeletionService,
$this->repository,
$this->serverVariableRepository,
$this->validatorService