From 564933fcb1e3f89bdbae08c23aa278895f6eba01 Mon Sep 17 00:00:00 2001 From: Charles Morgan Date: Sat, 16 May 2020 03:16:06 -0400 Subject: [PATCH 01/63] Show "unlimited" not 0 Shows unlimited for resources if they're set to "0" Im sure we could also just remove the "of {limit}" part if its unlimited? Dane Advise Im sure there is a 100x better way of doing this? Dane please advise. --- .../components/dashboard/ServerRow.tsx | 21 +++++++++++++++---- .../components/server/ServerConsole.tsx | 19 +++++++++++++++-- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/resources/scripts/components/dashboard/ServerRow.tsx b/resources/scripts/components/dashboard/ServerRow.tsx index e300e7611..d6d501b1d 100644 --- a/resources/scripts/components/dashboard/ServerRow.tsx +++ b/resources/scripts/components/dashboard/ServerRow.tsx @@ -53,6 +53,21 @@ export default ({ server, className }: { server: Server; className: string | und alarms.disk = server.limits.disk === 0 ? false : isAlarmState(stats.diskUsageInBytes, server.limits.disk); } + let disklimit; + let memorylimit; + + if(server.limits.disk != 0) { + disklimit = bytesToHuman(server.limits.disk * 1000 * 1000); + } else { + disklimit = "Unlimited"; + }; + + if(server.limits.memory != 0) { + memorylimit = bytesToHuman(server.limits.memory * 1000 * 1000); + } else { + memorylimit = "Unlimited"; + }; + return (
@@ -127,7 +142,7 @@ export default ({ server, className }: { server: Server; className: string | und {bytesToHuman(stats.memoryUsageInBytes)}

-

of {bytesToHuman(server.limits.memory * 1000 * 1000)}

+

of {memorylimit}

@@ -147,9 +162,7 @@ export default ({ server, className }: { server: Server; className: string | und {bytesToHuman(stats.diskUsageInBytes)}

-

- of {bytesToHuman(server.limits.disk * 1000 * 1000)} -

+

of {disklimit}

} diff --git a/resources/scripts/components/server/ServerConsole.tsx b/resources/scripts/components/server/ServerConsole.tsx index 5f5addf56..827cf6c3b 100644 --- a/resources/scripts/components/server/ServerConsole.tsx +++ b/resources/scripts/components/server/ServerConsole.tsx @@ -81,6 +81,21 @@ export default () => { }; }, [ instance, connected ]); + + let memorylimit; + let disklimit; + + if(server.limits.disk != 0 ) { + disklimit = / {server.limits.memory} MB; + } else { + disklimit = / Unlimited; + }; + if(server.limits.memory != 0 ) { + memorylimit = / {server.limits.memory} MB; + } else { + memorylimit = / Unlimited; + }; + return (
@@ -112,7 +127,7 @@ export default () => { className={'mr-1'} />  {bytesToHuman(memory)} - / {server.limits.memory} MB + {memorylimit}

{ className={'mr-1'} />  {bytesToHuman(disk)} - / {server.limits.disk} MB + {disklimit}

{!server.isInstalling ? From 3df0febd0cd7e69605d0f44d2a4d668fe5623862 Mon Sep 17 00:00:00 2001 From: Charles Morgan Date: Sun, 17 May 2020 00:34:25 -0400 Subject: [PATCH 02/63] Less Lines = Better Same logic, just more compact and react like --- .../scripts/components/dashboard/ServerRow.tsx | 17 ++--------------- .../scripts/components/server/ServerConsole.tsx | 16 ++-------------- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/resources/scripts/components/dashboard/ServerRow.tsx b/resources/scripts/components/dashboard/ServerRow.tsx index d6d501b1d..a212bce0c 100644 --- a/resources/scripts/components/dashboard/ServerRow.tsx +++ b/resources/scripts/components/dashboard/ServerRow.tsx @@ -52,21 +52,8 @@ export default ({ server, className }: { server: Server; className: string | und alarms.memory = isAlarmState(stats.memoryUsageInBytes, server.limits.memory); alarms.disk = server.limits.disk === 0 ? false : isAlarmState(stats.diskUsageInBytes, server.limits.disk); } - - let disklimit; - let memorylimit; - - if(server.limits.disk != 0) { - disklimit = bytesToHuman(server.limits.disk * 1000 * 1000); - } else { - disklimit = "Unlimited"; - }; - - if(server.limits.memory != 0) { - memorylimit = bytesToHuman(server.limits.memory * 1000 * 1000); - } else { - memorylimit = "Unlimited"; - }; + const disklimit = server.limits.disk != 0 ? bytesToHuman(server.limits.disk * 1000 * 1000) : "Unlimited"; + const memorylimit = server.limits.memory != 0 ? bytesToHuman(server.limits.memory * 1000 * 1000) : "Unlimited"; return ( diff --git a/resources/scripts/components/server/ServerConsole.tsx b/resources/scripts/components/server/ServerConsole.tsx index 827cf6c3b..9d2e99efd 100644 --- a/resources/scripts/components/server/ServerConsole.tsx +++ b/resources/scripts/components/server/ServerConsole.tsx @@ -81,20 +81,8 @@ export default () => { }; }, [ instance, connected ]); - - let memorylimit; - let disklimit; - - if(server.limits.disk != 0 ) { - disklimit = / {server.limits.memory} MB; - } else { - disklimit = / Unlimited; - }; - if(server.limits.memory != 0 ) { - memorylimit = / {server.limits.memory} MB; - } else { - memorylimit = / Unlimited; - }; + const disklimit = server.limits.disk != 0 ? bytesToHuman(server.limits.disk * 1000 * 1000) : "Unlimited"; + const memorylimit = server.limits.memory != 0 ? bytesToHuman(server.limits.memory * 1000 * 1000) : "Unlimited"; return ( From 917a1e8a87a99add44fc11eef04c9743c0a52b67 Mon Sep 17 00:00:00 2001 From: Charles Morgan Date: Mon, 18 May 2020 13:33:02 -0400 Subject: [PATCH 03/63] Update ServerConsole.tsx --- resources/scripts/components/server/ServerConsole.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/scripts/components/server/ServerConsole.tsx b/resources/scripts/components/server/ServerConsole.tsx index 9d2e99efd..16675b35e 100644 --- a/resources/scripts/components/server/ServerConsole.tsx +++ b/resources/scripts/components/server/ServerConsole.tsx @@ -115,8 +115,8 @@ export default () => { className={'mr-1'} />  {bytesToHuman(memory)} - {memorylimit} -

+ / {memorylimit} +

{ className={'mr-1'} />  {bytesToHuman(disk)} - {disklimit} + / {disklimit}

{!server.isInstalling ? From 6c60305617dd14d7b0ed3ad10cc58d9d215aac82 Mon Sep 17 00:00:00 2001 From: Charles Morgan Date: Sun, 24 May 2020 00:57:30 -0400 Subject: [PATCH 04/63] Show Unlimited instead of 0, admin view Guess i missed this one when i set everything to show unlimited when its 0 / -1. Shows Unlimited disk space instead of 0 on admin about page. --- resources/views/admin/servers/view/index.blade.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 From 48869d2eda81cc09ce593475f328e0e59a6bfc51 Mon Sep 17 00:00:00 2001 From: Charles Morgan Date: Tue, 2 Jun 2020 23:14:16 -0400 Subject: [PATCH 05/63] Remove create backup button if limit = 0, display message none can be created Removes the create button if the limit is 0, just like it does with the database page Also once limit is reached the create button is removed from view. --- .../components/server/backups/BackupContainer.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/resources/scripts/components/server/backups/BackupContainer.tsx b/resources/scripts/components/server/backups/BackupContainer.tsx index 438c201fb..1d162493c 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,17 @@ export default () => { />)}
} + {featureLimits.backups === 0 && +

+ Backups cannot be created for this server. +

+ } + {featureLimits.backups > 0 && featureLimits.backups !== backups.length &&
+ }
); From e3ac93359189b0cac74e3fc6508cc3f0a02ecb70 Mon Sep 17 00:00:00 2001 From: Charles Morgan Date: Tue, 2 Jun 2020 23:27:10 -0400 Subject: [PATCH 06/63] Add using x of x when limit > 0 Added a using x of x so the end user knows how many they have and are using. --- .../scripts/components/server/backups/BackupContainer.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/resources/scripts/components/server/backups/BackupContainer.tsx b/resources/scripts/components/server/backups/BackupContainer.tsx index 1d162493c..7a4b18d2f 100644 --- a/resources/scripts/components/server/backups/BackupContainer.tsx +++ b/resources/scripts/components/server/backups/BackupContainer.tsx @@ -37,6 +37,11 @@ export default () => { return ( + {featureLimits.backups >= 0 && +

+ You are currently using {backups.length} of {featureLimits.backups} backups. +

+ } {!backups.length ?

There are no backups stored for this server. From 2c326dcbe7c2323a162394badd3bca7594d31a2a Mon Sep 17 00:00:00 2001 From: Charles Morgan Date: Tue, 2 Jun 2020 23:30:43 -0400 Subject: [PATCH 07/63] Fix condition Fix condition for using message --- resources/scripts/components/server/backups/BackupContainer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/scripts/components/server/backups/BackupContainer.tsx b/resources/scripts/components/server/backups/BackupContainer.tsx index 7a4b18d2f..7e719aab9 100644 --- a/resources/scripts/components/server/backups/BackupContainer.tsx +++ b/resources/scripts/components/server/backups/BackupContainer.tsx @@ -37,7 +37,7 @@ export default () => { return ( - {featureLimits.backups >= 0 && + {featureLimits.backups !== 0 &&

You are currently using {backups.length} of {featureLimits.backups} backups.

From 5e6829026779ed6e2129ecec6be1058494a03526 Mon Sep 17 00:00:00 2001 From: Charles Morgan Date: Tue, 2 Jun 2020 23:40:40 -0400 Subject: [PATCH 08/63] Apply same logic to databases Same logic as PR #2088 Shows using x of x databases. Once limit is reached the create button is removed --- .../components/server/databases/DatabasesContainer.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/resources/scripts/components/server/databases/DatabasesContainer.tsx b/resources/scripts/components/server/databases/DatabasesContainer.tsx index 7fb60f351..77d498ee6 100644 --- a/resources/scripts/components/server/databases/DatabasesContainer.tsx +++ b/resources/scripts/components/server/databases/DatabasesContainer.tsx @@ -36,6 +36,11 @@ export default () => { return ( + {featureLimits.databases !== 0 && +

+ You are currently using {databases.length} of {featureLimits.databases} databases. +

+ } {(!databases.length && loading) ? : @@ -59,7 +64,7 @@ export default () => {

} - {featureLimits.databases > 0 && + {featureLimits.databases > 0 && featureLimits.databases !== databases.length &&
From b30d7429f35a7093e62e0cb1710bcca90b6a3266 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 13 Jun 2020 09:49:32 -0700 Subject: [PATCH 09/63] Decode the filename when rendering it to the screen; closes #2101 --- .../components/server/files/FileManagerBreadcrumbs.tsx | 6 +++--- .../scripts/components/server/files/NewDirectoryButton.tsx | 7 ++++++- resources/scripts/state/server/files.ts | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) 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(/^(\.\.\/|\/)+/, ''), + )} +

From 03f37e1fb9364dfa99afb99cbcf03645ea2ffa0f Mon Sep 17 00:00:00 2001 From: Charles Morgan Date: Sun, 14 Jun 2020 03:45:56 -0400 Subject: [PATCH 12/63] Add global scrollbar styling Addeding styling to the scroll bar so its not "out of place" from everything else. Open to changes to the style. Anything is better then what it was. Maybe remove the hover color? --- resources/styles/components/miscellaneous.css | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/resources/styles/components/miscellaneous.css b/resources/styles/components/miscellaneous.css index 48c51e8f3..95a2f030d 100644 --- a/resources/styles/components/miscellaneous.css +++ b/resources/styles/components/miscellaneous.css @@ -27,3 +27,39 @@ code.clean { @apply .mt-4; } } + +::-webkit-scrollbar { + background: none; + width: 16px; + height: 16px; +} + + ::-webkit-scrollbar-thumb { + border: solid 0 rgb(0 0 0 / 0%); + border-right-width: 4px; + border-left-width: 4px; + -webkit-border-radius: 9px 4px; + -webkit-box-shadow: inset 0 0 0 1px hsl(211, 10%, 53%), inset 0 0 0 4px hsl(209deg 18% 30%); +} + + ::-webkit-scrollbar-track-piece { + margin: 4px 0; +} + + ::-webkit-scrollbar-thumb:horizontal { + border-right-width: 0; + border-left-width: 0; + border-top-width: 4px; + border-bottom-width: 4px; + -webkit-border-radius: 4px 9px; +} + + ::-webkit-scrollbar-thumb:hover { + -webkit-box-shadow: + inset 0 0 0 1px hsl(212, 92%, 43%), + inset 0 0 0 4px hsl(212, 92%, 43%); +} + + ::-webkit-scrollbar-corner { + background: transparent; +} From ad0fd4bf725b96b5b72984e2c9741f9ac393bff6 Mon Sep 17 00:00:00 2001 From: Charles Morgan Date: Sun, 14 Jun 2020 03:47:37 -0400 Subject: [PATCH 13/63] Change syntax background Matches the background of file editor, keeps lighter border to say defiend. --- resources/styles/components/forms.css | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/styles/components/forms.css b/resources/styles/components/forms.css index 422ebc25c..2b1efbc1e 100644 --- a/resources/styles/components/forms.css +++ b/resources/styles/components/forms.css @@ -123,6 +123,7 @@ select.input:not(.appearance-none) { select.input-dark:not(.appearance-none) { @apply .bg-neutral-600 .border-neutral-500 .text-neutral-200; background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='%23C3D1DF' d='M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z'/%3e%3c/svg%3e "); + background-color: hsl(220deg 21% 16%); &:hover:not(:disabled), &:focus { @apply .border-neutral-400; From 89844e0a0dadea8d662f158909d4a205531d857e Mon Sep 17 00:00:00 2001 From: galeapatrik Date: Wed, 17 Jun 2020 21:32:33 +0200 Subject: [PATCH 14/63] Fix terminal css line formatting --- public/themes/pterodactyl/css/terminal.css | 1 + 1 file changed, 1 insertion(+) diff --git a/public/themes/pterodactyl/css/terminal.css b/public/themes/pterodactyl/css/terminal.css index a9bd3db1b..d2dc4d00f 100644 --- a/public/themes/pterodactyl/css/terminal.css +++ b/public/themes/pterodactyl/css/terminal.css @@ -27,6 +27,7 @@ #terminal > .cmd { padding: 1px 0; word-wrap: break-word; + white-space: pre-wrap; } #terminal_input { From 693b9eab0c5913cde6908697455cdbb3e7bc39ae Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 18 Jun 2020 21:00:04 -0700 Subject: [PATCH 15/63] Fix handling of backup tasks; closes #2067 --- .../Client/Servers/ScheduleTaskController.php | 4 +-- .../Servers/Schedules/StoreTaskRequest.php | 2 +- app/Models/Task.php | 2 +- .../server/schedules/ScheduleTaskRow.tsx | 33 +++++++++++++++---- .../server/schedules/TaskDetailsModal.tsx | 5 ++- 5 files changed, 35 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php index 7461a950a..77b824c92 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php @@ -58,7 +58,7 @@ class ScheduleTaskController extends ClientApiController 'schedule_id' => $schedule->id, 'sequence_id' => ($lastTask->sequence_id ?? 0) + 1, 'action' => $request->input('action'), - 'payload' => $request->input('payload'), + 'payload' => $request->input('payload') ?? '', 'time_offset' => $request->input('time_offset'), ]); @@ -87,7 +87,7 @@ class ScheduleTaskController extends ClientApiController $this->repository->update($task->id, [ 'action' => $request->input('action'), - 'payload' => $request->input('payload'), + 'payload' => $request->input('payload') ?? '', 'time_offset' => $request->input('time_offset'), ]); diff --git a/app/Http/Requests/Api/Client/Servers/Schedules/StoreTaskRequest.php b/app/Http/Requests/Api/Client/Servers/Schedules/StoreTaskRequest.php index 50fc9a2cb..cd95bf5e8 100644 --- a/app/Http/Requests/Api/Client/Servers/Schedules/StoreTaskRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Schedules/StoreTaskRequest.php @@ -25,7 +25,7 @@ class StoreTaskRequest extends ViewScheduleRequest { return [ 'action' => 'required|in:command,power,backup', - 'payload' => 'required_unless:action,backup|string', + 'payload' => 'required_unless:action,backup|string|nullable', 'time_offset' => 'required|numeric|min:0|max:900', 'sequence_id' => 'sometimes|required|numeric|min:1', ]; diff --git a/app/Models/Task.php b/app/Models/Task.php index f5a26b78a..f241b0717 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -90,7 +90,7 @@ class Task extends Model 'schedule_id' => 'required|numeric|exists:schedules,id', 'sequence_id' => 'required|numeric|min:1', 'action' => 'required|string', - 'payload' => 'required|string', + 'payload' => 'required_unless:action,backup|string', 'time_offset' => 'required|numeric|between:0,900', 'is_queued' => 'boolean', ]; diff --git a/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx b/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx index 516ffa9ae..f73f5da9d 100644 --- a/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx +++ b/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx @@ -14,12 +14,26 @@ import Can from '@/components/elements/Can'; import useServer from '@/plugins/useServer'; import useFlash from '@/plugins/useFlash'; import { ServerContext } from '@/state/server'; +import { faFileArchive } from '@fortawesome/free-solid-svg-icons/faFileArchive'; interface Props { schedule: Schedule; task: Task; } +const getActionDetails = (action: string): [ string, any ] => { + switch (action) { + case 'command': + return ['Send Command', faCode]; + case 'power': + return ['Send Power Action', faToggleOn]; + case 'backup': + return ['Create Backup', faFileArchive]; + default: + return ['Unknown Action', faCode]; + } +}; + export default ({ schedule, task }: Props) => { const { uuid } = useServer(); const { clearFlashes, addError } = useFlash(); @@ -43,6 +57,8 @@ export default ({ schedule, task }: Props) => { }); }; + const [ title, icon ] = getActionDetails(task.action); + return (
@@ -56,14 +72,19 @@ export default ({ schedule, task }: Props) => { onDismissed={() => setVisible(false)} onConfirmed={() => onConfirmDeletion()} /> - +
-

- {task.action === 'command' ? 'Send command' : 'Send power action'} +

+ {title}

- - {task.payload} - + {task.payload && +
+ {task.action === 'backup' &&

Ignoring files & folders:

} +
+ {task.payload} +
+
+ }
{task.sequenceId > 1 &&
diff --git a/resources/scripts/components/server/schedules/TaskDetailsModal.tsx b/resources/scripts/components/server/schedules/TaskDetailsModal.tsx index 7c6cdbc7b..1aef153db 100644 --- a/resources/scripts/components/server/schedules/TaskDetailsModal.tsx +++ b/resources/scripts/components/server/schedules/TaskDetailsModal.tsx @@ -71,7 +71,10 @@ const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => { :
- +
From b5b02207d33f0185b48b4e9f62d74f16362be80a Mon Sep 17 00:00:00 2001 From: Vilhelm Prytz Date: Mon, 22 Jun 2020 11:59:18 +0200 Subject: [PATCH 16/63] Update default value for MUMBLE_VERSION to latest version --- database/seeds/eggs/voice-servers/egg-mumble-server.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/seeds/eggs/voice-servers/egg-mumble-server.json b/database/seeds/eggs/voice-servers/egg-mumble-server.json index e62562597..00c87d21e 100644 --- a/database/seeds/eggs/voice-servers/egg-mumble-server.json +++ b/database/seeds/eggs/voice-servers/egg-mumble-server.json @@ -36,7 +36,7 @@ "name": "Server Version", "description": "Version of Mumble Server to download and use.", "env_variable": "MUMBLE_VERSION", - "default_value": "1.2.19", + "default_value": "1.3.1", "user_viewable": 1, "user_editable": 1, "rules": "required|regex:\/^([0-9_\\.-]{5,8})$\/" From 16e14621c89bbda1868a05ccadf0af029aee4281 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 22 Jun 2020 20:22:52 -0700 Subject: [PATCH 17/63] Better error messaging when server is suspended --- .../Middleware/Api/Client/Server/AuthenticateServerAccess.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php index c6acb66d3..1525755cf 100644 --- a/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php +++ b/app/Http/Middleware/Api/Client/Server/AuthenticateServerAccess.php @@ -65,7 +65,7 @@ class AuthenticateServerAccess } if ($server->suspended) { - throw new AccessDeniedHttpException('Cannot access a server that is marked as being suspended.'); + throw new AccessDeniedHttpException('This server is currenty suspended and the functionality requested is unavailable.'); } if (! $server->isInstalled()) { From b78aa180ea2109274f38220c4c17d3a387a7b68d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 23 Jun 2020 20:07:37 -0700 Subject: [PATCH 18/63] Prevent creating a new database via the application API if server is at its limit; closes #2129 --- .../Controllers/Admin/ServersController.php | 8 +- .../Servers/DatabaseController.php | 33 ++--- .../Api/Client/Servers/DatabaseController.php | 6 +- app/Models/Database.php | 15 +++ .../Databases/DatabaseManagementService.php | 114 +++++++++++------- .../Databases/DeployServerDatabaseService.php | 16 +-- 6 files changed, 116 insertions(+), 76 deletions(-) diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index cebbebe74..9dc3b8a13 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -312,12 +312,12 @@ class ServersController extends Controller * Creates a new database assigned to a specific server. * * @param \Pterodactyl\Http\Requests\Admin\Servers\Databases\StoreServerDatabaseRequest $request - * @param int $server + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\RedirectResponse * - * @throws \Exception + * @throws \Throwable */ - public function newDatabase(StoreServerDatabaseRequest $request, $server) + public function newDatabase(StoreServerDatabaseRequest $request, Server $server) { $this->databaseManagementService->create($server, [ 'database' => $request->input('database'), @@ -326,7 +326,7 @@ class ServersController extends Controller 'max_connections' => $request->input('max_connections'), ]); - return redirect()->route('admin.servers.view.database', $server)->withInput(); + return redirect()->route('admin.servers.view.database', $server->id)->withInput(); } /** diff --git a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php index f556aff57..24c8906aa 100644 --- a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php @@ -57,13 +57,12 @@ class DatabaseController extends ApplicationApiController * server. * * @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabasesRequest $request + * @param \Pterodactyl\Models\Server $server * @return array */ - public function index(GetServerDatabasesRequest $request): array + public function index(GetServerDatabasesRequest $request, Server $server): array { - $databases = $this->repository->getDatabasesForServer($request->getModel(Server::class)->id); - - return $this->fractal->collection($databases) + return $this->fractal->collection($server->databases) ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) ->toArray(); } @@ -72,11 +71,13 @@ class DatabaseController extends ApplicationApiController * Return a single server database. * * @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\GetServerDatabaseRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Database $database * @return array */ - public function view(GetServerDatabaseRequest $request): array + public function view(GetServerDatabaseRequest $request, Server $server, Database $database): array { - return $this->fractal->item($request->getModel(Database::class)) + return $this->fractal->item($database) ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) ->toArray(); } @@ -85,29 +86,31 @@ class DatabaseController extends ApplicationApiController * Reset the password for a specific server database. * * @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\ServerDatabaseWriteRequest $request - * @return \Illuminate\Http\Response + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Database $database + * @return \Illuminate\Http\JsonResponse * * @throws \Throwable */ - public function resetPassword(ServerDatabaseWriteRequest $request): Response + public function resetPassword(ServerDatabaseWriteRequest $request, Server $server, Database $database): JsonResponse { - $this->databasePasswordService->handle($request->getModel(Database::class)); + $this->databasePasswordService->handle($database); - return response('', 204); + return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT); } /** * Create a new database on the Panel for a given server. * * @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\StoreServerDatabaseRequest $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\JsonResponse * - * @throws \Exception + * @throws \Throwable */ - public function store(StoreServerDatabaseRequest $request): JsonResponse + public function store(StoreServerDatabaseRequest $request, Server $server): JsonResponse { - $server = $request->getModel(Server::class); - $database = $this->databaseManagementService->create($server->id, $request->validated()); + $database = $this->databaseManagementService->create($server, $request->validated()); return $this->fractal->item($database) ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) @@ -117,7 +120,7 @@ class DatabaseController extends ApplicationApiController 'database' => $database->id, ]), ]) - ->respond(201); + ->respond(Response::HTTP_CREATED); } /** diff --git a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php index 0c305c2e6..5a3e1e3ac 100644 --- a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php @@ -69,9 +69,7 @@ class DatabaseController extends ClientApiController */ public function index(GetDatabasesRequest $request, Server $server): array { - $databases = $this->repository->getDatabasesForServer($server->id); - - return $this->fractal->collection($databases) + return $this->fractal->collection($server->databases) ->transformWith($this->getTransformer(DatabaseTransformer::class)) ->toArray(); } @@ -83,6 +81,8 @@ class DatabaseController extends ClientApiController * @param \Pterodactyl\Models\Server $server * @return array * + * @throws \Throwable + * @throws \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException * @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException */ public function store(StoreDatabaseRequest $request, Server $server): array diff --git a/app/Models/Database.php b/app/Models/Database.php index ae20a51c8..42fbb1acc 100644 --- a/app/Models/Database.php +++ b/app/Models/Database.php @@ -2,6 +2,21 @@ namespace Pterodactyl\Models; +/** + * @property int $id + * @property int $server_id + * @property int $database_host_id + * @property string $database + * @property string $username + * @property string $remote + * @property string $password + * @property int $max_connections + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + * + * @property \Pterodactyl\Models\Server $server + * @property \Pterodactyl\Models\DatabaseHost $host + */ class Database extends Model { /** diff --git a/app/Services/Databases/DatabaseManagementService.php b/app/Services/Databases/DatabaseManagementService.php index b98a757db..7de6e2929 100644 --- a/app/Services/Databases/DatabaseManagementService.php +++ b/app/Services/Databases/DatabaseManagementService.php @@ -3,19 +3,22 @@ namespace Pterodactyl\Services\Databases; use Exception; +use Pterodactyl\Models\Server; use Pterodactyl\Models\Database; use Pterodactyl\Helpers\Utilities; -use Illuminate\Database\DatabaseManager; +use Illuminate\Database\ConnectionInterface; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Extensions\DynamicDatabaseConnection; use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; +use Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException; +use Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException; class DatabaseManagementService { /** - * @var \Illuminate\Database\DatabaseManager + * @var \Illuminate\Database\ConnectionInterface */ - private $database; + private $connection; /** * @var \Pterodactyl\Extensions\DynamicDatabaseConnection @@ -33,84 +36,113 @@ class DatabaseManagementService private $repository; /** + * Determines if the service should validate the user's ability to create an additional + * database for this server. In almost all cases this should be true, but to keep things + * flexible you can also set it to false and create more databases than the server is + * allocated. + * * @var bool */ - protected $useRandomHost = false; + protected $validateDatabaseLimit = true; /** * CreationService constructor. * - * @param \Illuminate\Database\DatabaseManager $database + * @param \Illuminate\Database\ConnectionInterface $connection * @param \Pterodactyl\Extensions\DynamicDatabaseConnection $dynamic * @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter */ public function __construct( - DatabaseManager $database, + ConnectionInterface $connection, DynamicDatabaseConnection $dynamic, DatabaseRepositoryInterface $repository, Encrypter $encrypter ) { - $this->database = $database; + $this->connection = $connection; $this->dynamic = $dynamic; $this->encrypter = $encrypter; $this->repository = $repository; } + /** + * Set wether or not this class should validate that the server has enough slots + * left before creating the new database. + * + * @param bool $validate + * @return $this + */ + public function setValidateDatabaseLimit(bool $validate): self + { + $this->validateDatabaseLimit = $validate; + + return $this; + } + /** * Create a new database that is linked to a specific host. * - * @param int $server + * @param \Pterodactyl\Models\Server $server * @param array $data * @return \Pterodactyl\Models\Database * - * @throws \Exception + * @throws \Throwable + * @throws \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException + * @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException */ - public function create($server, array $data) + public function create(Server $server, array $data) { - $data['server_id'] = $server; - $data['database'] = sprintf('s%d_%s', $server, $data['database']); - $data['username'] = sprintf('u%d_%s', $server, str_random(10)); - $data['password'] = $this->encrypter->encrypt( - Utilities::randomStringWithSpecialCharacters(24) - ); + if (! config('pterodactyl.client_features.databases.enabled')) { + throw new DatabaseClientFeatureNotEnabledException; + } + + if ($this->validateDatabaseLimit) { + // If the server has a limit assigned and we've already reached that limit, throw back + // an exception and kill the process. + if (! is_null($server->database_limit) && $server->databases()->count() >= $server->database_limit) { + throw new TooManyDatabasesException; + } + } + + $data = array_merge($data, [ + 'server_id' => $server->id, + 'database' => sprintf('s%d_%s', $server->id, $data['database']), + 'username' => sprintf('u%d_%s', $server->id, str_random(10)), + 'password' => $this->encrypter->encrypt( + Utilities::randomStringWithSpecialCharacters(24) + ), + ]); + + $database = null; - $this->database->beginTransaction(); try { - $database = $this->repository->createIfNotExists($data); - $this->dynamic->set('dynamic', $data['database_host_id']); + return $this->connection->transaction(function () use ($data, &$database) { + $database = $this->repository->createIfNotExists($data); + $this->dynamic->set('dynamic', $data['database_host_id']); - $this->repository->createDatabase($database->database); - $this->repository->createUser( - $database->username, - $database->remote, - $this->encrypter->decrypt($database->password), - $database->max_connections - ); - $this->repository->assignUserToDatabase( - $database->database, - $database->username, - $database->remote - ); - $this->repository->flush(); + $this->repository->createDatabase($database->database); + $this->repository->createUser( + $database->username, $database->remote, $this->encrypter->decrypt($database->password), $database->max_connections + ); + $this->repository->assignUserToDatabase($database->database, $database->username, $database->remote); + $this->repository->flush(); - $this->database->commit(); - } catch (Exception $ex) { + return $database; + }); + } catch (Exception $exception) { try { - if (isset($database) && $database instanceof Database) { + if ($database instanceof Database) { $this->repository->dropDatabase($database->database); $this->repository->dropUser($database->username, $database->remote); $this->repository->flush(); } - } catch (Exception $exTwo) { - // ignore an exception + } catch (Exception $exception) { + // Do nothing here. We've already encountered an issue before this point so no + // reason to prioritize this error over the initial one. } - $this->database->rollBack(); - throw $ex; + throw $exception; } - - return $database; } /** diff --git a/app/Services/Databases/DeployServerDatabaseService.php b/app/Services/Databases/DeployServerDatabaseService.php index b48f9e6f4..734740324 100644 --- a/app/Services/Databases/DeployServerDatabaseService.php +++ b/app/Services/Databases/DeployServerDatabaseService.php @@ -6,9 +6,7 @@ use Pterodactyl\Models\Server; use Pterodactyl\Models\Database; use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface; use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface; -use Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException; use Pterodactyl\Exceptions\Service\Database\NoSuitableDatabaseHostException; -use Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException; class DeployServerDatabaseService { @@ -49,20 +47,12 @@ class DeployServerDatabaseService * @param array $data * @return \Pterodactyl\Models\Database * + * @throws \Throwable + * @throws \Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException * @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException - * @throws \Exception */ public function handle(Server $server, array $data): Database { - if (! config('pterodactyl.client_features.databases.enabled')) { - throw new DatabaseClientFeatureNotEnabledException; - } - - $databases = $this->repository->findCountWhere([['server_id', '=', $server->id]]); - if (! is_null($server->database_limit) && $databases >= $server->database_limit) { - throw new TooManyDatabasesException; - } - $allowRandom = config('pterodactyl.client_features.databases.allow_random'); $hosts = $this->databaseHostRepository->setColumns(['id'])->findWhere([ ['node_id', '=', $server->node_id], @@ -81,7 +71,7 @@ class DeployServerDatabaseService $host = $hosts->random(); - return $this->managementService->create($server->id, [ + return $this->managementService->create($server, [ 'database_host_id' => $host->id, 'database' => array_get($data, 'database'), 'remote' => array_get($data, 'remote'), From 497f73d820b129d7e8856bd9658d06b5f380c336 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 23 Jun 2020 20:13:58 -0700 Subject: [PATCH 19/63] Only show limit warnings when necessary and relevant --- .../components/server/backups/BackupContainer.tsx | 10 +++++----- .../components/server/databases/DatabasesContainer.tsx | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/resources/scripts/components/server/backups/BackupContainer.tsx b/resources/scripts/components/server/backups/BackupContainer.tsx index 7e719aab9..1dbe3070e 100644 --- a/resources/scripts/components/server/backups/BackupContainer.tsx +++ b/resources/scripts/components/server/backups/BackupContainer.tsx @@ -37,11 +37,6 @@ export default () => { return ( - {featureLimits.backups !== 0 && -

- You are currently using {backups.length} of {featureLimits.backups} backups. -

- } {!backups.length ?

There are no backups stored for this server. @@ -61,6 +56,11 @@ export default () => {

} + {(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 77d498ee6..9213347f0 100644 --- a/resources/scripts/components/server/databases/DatabasesContainer.tsx +++ b/resources/scripts/components/server/databases/DatabasesContainer.tsx @@ -36,11 +36,6 @@ export default () => { return ( - {featureLimits.databases !== 0 && -

- You are currently using {databases.length} of {featureLimits.databases} databases. -

- } {(!databases.length && loading) ? : @@ -64,6 +59,11 @@ export default () => {

} + {(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 &&
From 4a0627d182b1e57eb3b63d277f50371ecd952b1a Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 23 Jun 2020 20:24:09 -0700 Subject: [PATCH 20/63] Don't trigger a 500 error due to unchecked data being inserted; closes #2087 This also clears up allowed values for the disk input and normalizes the messaging between edit and create screens. --- .../Controllers/Admin/ServersController.php | 19 +++++++++++++------ .../Servers/BuildModificationService.php | 4 ++-- resources/views/admin/servers/new.blade.php | 9 ++++----- .../views/admin/servers/view/build.blade.php | 2 +- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 9dc3b8a13..e163804cb 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -15,9 +15,11 @@ use Pterodactyl\Models\Server; use Prologue\Alerts\AlertsMessageBag; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; +use Illuminate\Validation\ValidationException; use Pterodactyl\Services\Servers\SuspensionService; use Pterodactyl\Services\Servers\ServerDeletionService; use Pterodactyl\Services\Servers\ReinstallServerService; +use Pterodactyl\Exceptions\Model\DataValidationException; use Pterodactyl\Services\Servers\BuildModificationService; use Pterodactyl\Services\Databases\DatabasePasswordService; use Pterodactyl\Services\Servers\DetailsModificationService; @@ -255,16 +257,21 @@ class ServersController extends Controller * @return \Illuminate\Http\RedirectResponse * * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Illuminate\Validation\ValidationException */ public function updateBuild(Request $request, Server $server) { - $this->buildModificationService->handle($server, $request->only([ - 'allocation_id', 'add_allocations', 'remove_allocations', - 'memory', 'swap', 'io', 'cpu', 'threads', 'disk', - 'database_limit', 'allocation_limit', 'backup_limit', 'oom_disabled', - ])); + try { + $this->buildModificationService->handle($server, $request->only([ + 'allocation_id', 'add_allocations', 'remove_allocations', + 'memory', 'swap', 'io', 'cpu', 'threads', 'disk', + 'database_limit', 'allocation_limit', 'backup_limit', 'oom_disabled', + ])); + } catch (DataValidationException $exception) { + throw new ValidationException($exception->validator); + } + $this->alert->success(trans('admin/server.alerts.build_updated'))->flash(); return redirect()->route('admin.servers.view.build', $server->id); diff --git a/app/Services/Servers/BuildModificationService.php b/app/Services/Servers/BuildModificationService.php index 5f4b10d34..b6768fdb1 100644 --- a/app/Services/Servers/BuildModificationService.php +++ b/app/Services/Servers/BuildModificationService.php @@ -71,8 +71,8 @@ class BuildModificationService * @return \Pterodactyl\Models\Server * * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ public function handle(Server $server, array $data) { @@ -91,7 +91,7 @@ class BuildModificationService } } - /** @var \Pterodactyl\Models\Server $server */ + /* @var \Pterodactyl\Models\Server $server */ $server = $this->repository->withFreshModel()->update($server->id, [ 'oom_disabled' => array_get($data, 'oom_disabled'), 'memory' => array_get($data, 'memory'), diff --git a/resources/views/admin/servers/new.blade.php b/resources/views/admin/servers/new.blade.php index d72a80abd..026be8be3 100644 --- a/resources/views/admin/servers/new.blade.php +++ b/resources/views/admin/servers/new.blade.php @@ -176,6 +176,8 @@ MB
+ +

The maximum amount of memory allowed for this container. Setting this to 0 will allow unlimited memory in a container.

@@ -185,21 +187,18 @@ MB
+

Setting this to 0 will disable swap space on this server. Setting to -1 will allow unlimited swap.

- -
-
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. Set to 0 to allow unlimited disk usage.

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.

From 066ed5cddad1f12909c6dfa5c0f05120ec1e364f Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 23 Jun 2020 20:26:48 -0700 Subject: [PATCH 21/63] Don't prevent deletion if a DB host is unreachable and it is a force delete; closes #2085 --- app/Services/Servers/ServerDeletionService.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/Services/Servers/ServerDeletionService.php b/app/Services/Servers/ServerDeletionService.php index d4ce9a896..8d7217769 100644 --- a/app/Services/Servers/ServerDeletionService.php +++ b/app/Services/Servers/ServerDeletionService.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Services\Servers; +use Exception; use Psr\Log\LoggerInterface; use Pterodactyl\Models\Server; use Illuminate\Database\ConnectionInterface; @@ -109,7 +110,15 @@ class ServerDeletionService $this->connection->transaction(function () use ($server) { $this->databaseRepository->setColumns('id')->findWhere([['server_id', '=', $server->id]])->each(function ($item) { - $this->databaseManagementService->delete($item->id); + try { + $this->databaseManagementService->delete($item->id); + } catch (Exception $exception) { + if ($this->force) { + $this->writer->warning($exception); + } else { + throw $exception; + } + } }); $this->repository->delete($server->id); From ba0c78d2ab7a68fd2dabd5f7bdfe7c30d6c8777f Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 23 Jun 2020 21:00:00 -0700 Subject: [PATCH 22/63] Revert "Add missing testing dependencies" This reverts commit 6ed35ad83c7985c696b503f25b8405d375cadde8. --- composer.json | 2 - composer.lock | 559 +++++++++++++++++++++++++++++++++++--------------- 2 files changed, 395 insertions(+), 166 deletions(-) diff --git a/composer.json b/composer.json index 0a996b06d..c8cd7a290 100644 --- a/composer.json +++ b/composer.json @@ -44,9 +44,7 @@ "barryvdh/laravel-ide-helper": "^2.6", "codedungeon/phpunit-result-printer": "0.25.1", "friendsofphp/php-cs-fixer": "^2.16.1", - "fzaninotto/faker": "^1.9.1", "laravel/dusk": "^5.11", - "mockery/mockery": "^1.0", "php-mock/php-mock-phpunit": "^2.6", "phpunit/phpunit": "^7" }, diff --git a/composer.lock b/composer.lock index d4dfac2db..0a6709686 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b6a885effd46e896377825b057886f23", + "content-hash": "2318244deed430b3f0b3df9ee4977759", "packages": [ { "name": "appstract/laravel-blade-directives", @@ -1655,6 +1655,12 @@ "sftp", "storage" ], + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], "time": "2020-03-17T18:58:12+00:00" }, { @@ -2070,6 +2076,16 @@ "datetime", "time" ], + "funding": [ + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], "time": "2020-03-31T13:43:19+00:00" }, { @@ -3082,6 +3098,12 @@ "spatie", "transform" ], + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + } + ], "time": "2020-03-02T18:40:49+00:00" }, { @@ -3264,6 +3286,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-30T11:41:10+00:00" }, { @@ -3317,6 +3353,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:56:45+00:00" }, { @@ -3373,6 +3423,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:54:36+00:00" }, { @@ -3429,6 +3493,20 @@ ], "description": "Symfony ErrorHandler Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-30T14:07:33+00:00" }, { @@ -3499,6 +3577,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:54:36+00:00" }, { @@ -3606,6 +3698,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:54:36+00:00" }, { @@ -3661,6 +3767,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-30T14:07:33+00:00" }, { @@ -3751,6 +3871,20 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-30T14:59:15+00:00" }, { @@ -3813,6 +3947,20 @@ "mime", "mime-type" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:56:45+00:00" }, { @@ -3871,6 +4019,20 @@ "polyfill", "portable" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-02-27T09:26:54+00:00" }, { @@ -3930,6 +4092,20 @@ "portable", "shim" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-09T19:04:49+00:00" }, { @@ -3992,6 +4168,20 @@ "portable", "shim" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-09T19:04:49+00:00" }, { @@ -4051,6 +4241,20 @@ "portable", "shim" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-09T19:04:49+00:00" }, { @@ -4107,6 +4311,20 @@ "portable", "shim" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-09T19:04:49+00:00" }, { @@ -4162,6 +4380,20 @@ "portable", "shim" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-02-27T09:26:54+00:00" }, { @@ -4220,6 +4452,20 @@ "portable", "shim" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-02-27T09:26:54+00:00" }, { @@ -4272,6 +4518,20 @@ "polyfill", "shim" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-02T11:55:35+00:00" }, { @@ -4321,6 +4581,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:54:36+00:00" }, { @@ -4397,6 +4671,20 @@ "uri", "url" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-30T11:41:10+00:00" }, { @@ -4531,6 +4819,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:54:36+00:00" }, { @@ -4664,6 +4966,20 @@ "debug", "dump" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:54:36+00:00" }, { @@ -4723,6 +5039,20 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-30T11:41:10+00:00" }, { @@ -4835,6 +5165,12 @@ "env", "environment" ], + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], "time": "2020-03-27T23:36:02+00:00" }, { @@ -5351,6 +5687,16 @@ "dependency", "package" ], + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], "time": "2020-03-13T19:34:27+00:00" }, { @@ -5516,6 +5862,12 @@ "Xdebug", "performance" ], + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + } + ], "time": "2020-03-01T12:26:26+00:00" }, { @@ -5732,104 +6084,6 @@ "description": "A tool to automatically fix PHP code style", "time": "2019-11-25T22:10:32+00:00" }, - { - "name": "fzaninotto/faker", - "version": "v1.9.1", - "source": { - "type": "git", - "url": "https://github.com/fzaninotto/Faker.git", - "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/fc10d778e4b84d5bd315dad194661e091d307c6f", - "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "ext-intl": "*", - "phpunit/phpunit": "^4.8.35 || ^5.7", - "squizlabs/php_codesniffer": "^2.9.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.9-dev" - } - }, - "autoload": { - "psr-4": { - "Faker\\": "src/Faker/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "François Zaninotto" - } - ], - "description": "Faker is a PHP library that generates fake data for you.", - "keywords": [ - "data", - "faker", - "fixtures" - ], - "time": "2019-12-12T13:22:17+00:00" - }, - { - "name": "hamcrest/hamcrest-php", - "version": "v2.0.0", - "source": { - "type": "git", - "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/776503d3a8e85d4f9a1148614f95b7a608b046ad", - "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad", - "shasum": "" - }, - "require": { - "php": "^5.3|^7.0" - }, - "replace": { - "cordoval/hamcrest-php": "*", - "davedevelopment/hamcrest-php": "*", - "kodova/hamcrest-php": "*" - }, - "require-dev": { - "phpunit/php-file-iterator": "1.3.3", - "phpunit/phpunit": "~4.0", - "satooshi/php-coveralls": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "hamcrest" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD" - ], - "description": "This is the PHP port of Hamcrest Matchers", - "keywords": [ - "test" - ], - "time": "2016-01-20T08:20:44+00:00" - }, { "name": "hassankhan/config", "version": "0.11.2", @@ -6082,71 +6336,6 @@ ], "time": "2019-11-24T09:46:11+00:00" }, - { - "name": "mockery/mockery", - "version": "1.3.1", - "source": { - "type": "git", - "url": "https://github.com/mockery/mockery.git", - "reference": "f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be", - "reference": "f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be", - "shasum": "" - }, - "require": { - "hamcrest/hamcrest-php": "~2.0", - "lib-pcre": ">=7.0", - "php": ">=5.6.0" - }, - "require-dev": { - "phpunit/phpunit": "~5.7.10|~6.5|~7.0|~8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "psr-0": { - "Mockery": "library/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Pádraic Brady", - "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" - }, - { - "name": "Dave Marshall", - "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "http://davedevelopment.co.uk" - } - ], - "description": "Mockery is a simple yet flexible PHP mock object framework", - "homepage": "https://github.com/mockery/mockery", - "keywords": [ - "BDD", - "TDD", - "library", - "mock", - "mock objects", - "mockery", - "stub", - "test", - "test double", - "testing" - ], - "time": "2019-12-26T09:49:15+00:00" - }, { "name": "myclabs/deep-copy", "version": "1.9.5", @@ -7841,6 +8030,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:56:45+00:00" }, { @@ -7895,6 +8098,20 @@ "configuration", "options" ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:56:45+00:00" }, { @@ -8004,6 +8221,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:56:45+00:00" }, { From c3aa257a2becbf28dc338d2d1bd2ae160eb2a38a Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 23 Jun 2020 21:24:37 -0700 Subject: [PATCH 23/63] Update dependencies to latest versions --- app/Exceptions/Handler.php | 23 +- composer.json | 22 +- composer.lock | 2483 ++++++++++++----- .../Server/BulkPowerActionCommandTest.php | 13 +- 4 files changed, 1742 insertions(+), 799 deletions(-) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 39709d5e2..50ac1a960 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Exceptions; use Exception; +use Throwable; use PDOException; use Psr\Log\LoggerInterface; use Swift_TransportException; @@ -72,12 +73,12 @@ class Handler extends ExceptionHandler * services such as AWS Cloudwatch or other monitoring you can replace the * contents of this function with a call to the parent reporter. * - * @param \Exception $exception + * @param \Throwable $exception * @return mixed * - * @throws \Exception + * @throws \Throwable */ - public function report(Exception $exception) + public function report(Throwable $exception) { if (! config('app.exceptions.report_all', false) && $this->shouldntReport($exception)) { return null; @@ -103,7 +104,7 @@ class Handler extends ExceptionHandler return $logger->error($exception); } - private function generateCleanedExceptionStack(Exception $exception) + private function generateCleanedExceptionStack(Throwable $exception) { $cleanedStack = ''; foreach ($exception->getTrace() as $index => $item) { @@ -133,12 +134,12 @@ class Handler extends ExceptionHandler * Render an exception into an HTTP response. * * @param \Illuminate\Http\Request $request - * @param \Exception $exception + * @param \Throwable $exception * @return \Symfony\Component\HttpFoundation\Response * - * @throws \Exception + * @throws \Throwable */ - public function render($request, Exception $exception) + public function render($request, Throwable $exception) { $connections = Container::getInstance()->make(Connection::class); @@ -200,11 +201,11 @@ class Handler extends ExceptionHandler /** * Return the exception as a JSONAPI representation for use on API requests. * - * @param \Exception $exception + * @param \Throwable $exception * @param array $override * @return array */ - public static function convertToArray(Exception $exception, array $override = []): array + public static function convertToArray(Throwable $exception, array $override = []): array { $error = [ 'code' => class_basename($exception), @@ -259,10 +260,10 @@ class Handler extends ExceptionHandler * Converts an exception into an array to render in the response. Overrides * Laravel's built-in converter to output as a JSONAPI spec compliant object. * - * @param \Exception $exception + * @param \Throwable $exception * @return array */ - protected function convertExceptionToArray(Exception $exception) + protected function convertExceptionToArray(Throwable $exception) { return self::convertToArray($exception); } diff --git a/composer.json b/composer.json index c8cd7a290..b8de45ca0 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ } ], "require": { - "php": ">=7.2", + "php": "^7.2", "ext-mbstring": "*", "ext-pdo_mysql": "*", "ext-zip": "*", @@ -23,9 +23,10 @@ "guzzlehttp/guzzle": "^6.5", "hashids/hashids": "^4.0", "laracasts/utilities": "^3.1", - "laravel/framework": "^6.18", + "laravel/framework": "^7.17", "laravel/helpers": "^1.2", - "laravel/tinker": "^1.0", + "laravel/tinker": "^2.4", + "laravel/ui": "^2.0", "lcobucci/jwt": "^3.3", "league/flysystem-aws-s3-v3": "^1.0", "league/flysystem-memory": "^1.0", @@ -33,20 +34,21 @@ "pragmarx/google2fa": "^5.0", "predis/predis": "^1.1", "prologue/alerts": "^0.4", + "psy/psysh": "^0.10.4", "s1lentium/iptools": "^1.1", "spatie/laravel-fractal": "^5.7", - "staudenmeir/belongs-to-through": "^2.9", + "staudenmeir/belongs-to-through": "^2.10", "symfony/yaml": "^4.4", - "webmozart/assert": "^1.7" + "webmozart/assert": "^1.9" }, "require-dev": { - "barryvdh/laravel-debugbar": "^3.2", - "barryvdh/laravel-ide-helper": "^2.6", + "barryvdh/laravel-debugbar": "^3.3", + "barryvdh/laravel-ide-helper": "^2.7", "codedungeon/phpunit-result-printer": "0.25.1", - "friendsofphp/php-cs-fixer": "^2.16.1", - "laravel/dusk": "^5.11", + "friendsofphp/php-cs-fixer": "2.16.1", + "laravel/dusk": "^6.3", "php-mock/php-mock-phpunit": "^2.6", - "phpunit/phpunit": "^7" + "phpunit/phpunit": "^8.5" }, "autoload": { "classmap": [ diff --git a/composer.lock b/composer.lock index 0a6709686..ad2234816 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2318244deed430b3f0b3df9ee4977759", + "content-hash": "394f932491c6133156e0a887ca7eebfb", "packages": [ { "name": "appstract/laravel-blade-directives", - "version": "1.8.0", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/appstract/laravel-blade-directives.git", - "reference": "ac9958e5499d21b12c317a43d96e41c00eaa3fba" + "reference": "c569529da9bf4d87c85157061c5e86b5ee1f8ff4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appstract/laravel-blade-directives/zipball/ac9958e5499d21b12c317a43d96e41c00eaa3fba", - "reference": "ac9958e5499d21b12c317a43d96e41c00eaa3fba", + "url": "https://api.github.com/repos/appstract/laravel-blade-directives/zipball/c569529da9bf4d87c85157061c5e86b5ee1f8ff4", + "reference": "c569529da9bf4d87c85157061c5e86b5ee1f8ff4", "shasum": "" }, "require": { @@ -25,8 +25,7 @@ "php": "^7.1.3" }, "require-dev": { - "orchestra/testbench": "~3.7|^4.0", - "phpunit/phpunit": "^7.0" + "orchestra/testbench": "~3.7|^4.0|^5.0" }, "type": "library", "extra": { @@ -59,20 +58,20 @@ "appstract", "laravel-blade-directives" ], - "time": "2020-03-04T08:57:34+00:00" + "time": "2020-04-14T08:41:18+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.134.3", + "version": "3.142.8", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "3de2711a47e7c3f5e93a5c83f019188fd23f852f" + "reference": "a9cffb2442b63ca8a99ed3e68aef19df221e2a54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3de2711a47e7c3f5e93a5c83f019188fd23f852f", - "reference": "3de2711a47e7c3f5e93a5c83f019188fd23f852f", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a9cffb2442b63ca8a99ed3e68aef19df221e2a54", + "reference": "a9cffb2442b63ca8a99ed3e68aef19df221e2a54", "shasum": "" }, "require": { @@ -95,6 +94,7 @@ "ext-pcntl": "*", "ext-sockets": "*", "nette/neon": "^2.3", + "paragonie/random_compat": ">= 2", "phpunit/phpunit": "^4.8.35|^5.4.3", "psr/cache": "^1.0", "psr/simple-cache": "^1.0", @@ -143,7 +143,59 @@ "s3", "sdk" ], - "time": "2020-04-03T18:11:51+00:00" + "time": "2020-06-23T18:46:28+00:00" + }, + { + "name": "brick/math", + "version": "0.8.15", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "9b08d412b9da9455b210459ff71414de7e6241cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/9b08d412b9da9455b210459ff71414de7e6241cd", + "reference": "9b08d412b9da9455b210459ff71414de7e6241cd", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.1|^8.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^7.5.15|^8.5", + "vimeo/psalm": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "brick", + "math" + ], + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/brick/math", + "type": "tidelift" + } + ], + "time": "2020-04-15T15:59:35+00:00" }, { "name": "cakephp/chronos", @@ -236,20 +288,20 @@ }, { "name": "doctrine/cache", - "version": "1.10.0", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "382e7f4db9a12dc6c19431743a2b096041bcdd62" + "reference": "35a4a70cd94e09e2259dfae7488afc6b474ecbd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/382e7f4db9a12dc6c19431743a2b096041bcdd62", - "reference": "382e7f4db9a12dc6c19431743a2b096041bcdd62", + "url": "https://api.github.com/repos/doctrine/cache/zipball/35a4a70cd94e09e2259dfae7488afc6b474ecbd3", + "reference": "35a4a70cd94e09e2259dfae7488afc6b474ecbd3", "shasum": "" }, "require": { - "php": "~7.1" + "php": "~7.1 || ^8.0" }, "conflict": { "doctrine/common": ">2.2,<2.4" @@ -314,20 +366,34 @@ "redis", "xcache" ], - "time": "2019-11-29T15:36:20+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "time": "2020-05-27T16:24:54+00:00" }, { "name": "doctrine/dbal", - "version": "v2.10.1", + "version": "2.10.2", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8" + "reference": "aab745e7b6b2de3b47019da81e7225e14dcfdac8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8", - "reference": "c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/aab745e7b6b2de3b47019da81e7225e14dcfdac8", + "reference": "aab745e7b6b2de3b47019da81e7225e14dcfdac8", "shasum": "" }, "require": { @@ -339,9 +405,11 @@ "require-dev": { "doctrine/coding-standard": "^6.0", "jetbrains/phpstorm-stubs": "^2019.1", - "phpstan/phpstan": "^0.11.3", + "nikic/php-parser": "^4.4", + "phpstan/phpstan": "^0.12", "phpunit/phpunit": "^8.4.1", - "symfony/console": "^2.0.5|^3.0|^4.0|^5.0" + "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", + "vimeo/psalm": "^3.11" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -406,7 +474,21 @@ "sqlserver", "sqlsrv" ], - "time": "2020-01-04T12:56:21+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "type": "tidelift" + } + ], + "time": "2020-04-20T17:19:26+00:00" }, { "name": "doctrine/event-manager", @@ -486,33 +568,37 @@ }, { "name": "doctrine/inflector", - "version": "1.3.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1" + "reference": "9cf661f4eb38f7c881cac67c75ea9b00bf97b210" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/ec3a55242203ffa6a4b27c58176da97ff0a7aec1", - "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/9cf661f4eb38f7c881cac67c75ea9b00bf97b210", + "reference": "9cf661f4eb38f7c881cac67c75ea9b00bf97b210", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^6.2" + "doctrine/coding-standard": "^7.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-strict-rules": "^0.11", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" } }, "notification-url": "https://packagist.org/downloads/", @@ -541,32 +627,52 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Common String Manipulations with regard to casing and singular/plural rules.", - "homepage": "http://www.doctrine-project.org", + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", "keywords": [ "inflection", - "pluralize", - "singularize", - "string" + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" ], - "time": "2019-10-30T19:59:35+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2020-05-29T15:13:26+00:00" }, { "name": "doctrine/lexer", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6" + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6", - "reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", "shasum": "" }, "require": { - "php": "^7.2" + "php": "^7.2 || ^8.0" }, "require-dev": { "doctrine/coding-standard": "^6.0", @@ -611,7 +717,21 @@ "parser", "php" ], - "time": "2019-10-30T14:39:59+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2020-05-25T17:44:05+00:00" }, { "name": "dragonmantank/cron-expression", @@ -669,16 +789,16 @@ }, { "name": "egulias/email-validator", - "version": "2.1.17", + "version": "2.1.18", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "ade6887fd9bd74177769645ab5c474824f8a418a" + "reference": "cfa3d44471c7f5bfb684ac2b0da7114283d78441" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ade6887fd9bd74177769645ab5c474824f8a418a", - "reference": "ade6887fd9bd74177769645ab5c474824f8a418a", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/cfa3d44471c7f5bfb684ac2b0da7114283d78441", + "reference": "cfa3d44471c7f5bfb684ac2b0da7114283d78441", "shasum": "" }, "require": { @@ -702,7 +822,7 @@ }, "autoload": { "psr-4": { - "Egulias\\EmailValidator\\": "EmailValidator" + "Egulias\\EmailValidator\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -723,20 +843,20 @@ "validation", "validator" ], - "time": "2020-02-13T22:36:52+00:00" + "time": "2020-06-16T20:11:17+00:00" }, { "name": "fideloper/proxy", - "version": "4.3.0", + "version": "4.4.0", "source": { "type": "git", "url": "https://github.com/fideloper/TrustedProxy.git", - "reference": "ec38ad69ee378a1eec04fb0e417a97cfaf7ed11a" + "reference": "9beebf48a1c344ed67c1d36bb1b8709db7c3c1a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/ec38ad69ee378a1eec04fb0e417a97cfaf7ed11a", - "reference": "ec38ad69ee378a1eec04fb0e417a97cfaf7ed11a", + "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/9beebf48a1c344ed67c1d36bb1b8709db7c3c1a8", + "reference": "9beebf48a1c344ed67c1d36bb1b8709db7c3c1a8", "shasum": "" }, "require": { @@ -777,27 +897,28 @@ "proxy", "trusted proxy" ], - "time": "2020-02-22T01:51:47+00:00" + "time": "2020-06-23T01:36:47+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "6.5.2", + "version": "6.5.5", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "43ece0e75098b7ecd8d13918293029e555a50f82" + "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/43ece0e75098b7ecd8d13918293029e555a50f82", - "reference": "43ece0e75098b7ecd8d13918293029e555a50f82", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/9d4290de1cfd701f38099ef7e183b64b4b7b0c5e", + "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/promises": "^1.0", "guzzlehttp/psr7": "^1.6.1", - "php": ">=5.5" + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.17.0" }, "require-dev": { "ext-curl": "*", @@ -805,7 +926,6 @@ "psr/log": "^1.1" }, "suggest": { - "ext-intl": "Required for Internationalized Domain Name (IDN) support", "psr/log": "Required for using the Log middleware" }, "type": "library", @@ -844,7 +964,7 @@ "rest", "web service" ], - "time": "2019-12-23T11:57:10+00:00" + "time": "2020-06-16T21:01:06+00:00" }, { "name": "guzzlehttp/promises", @@ -1035,96 +1155,6 @@ ], "time": "2019-04-03T13:40:29+00:00" }, - { - "name": "jakub-onderka/php-console-color", - "version": "v0.2", - "source": { - "type": "git", - "url": "https://github.com/JakubOnderka/PHP-Console-Color.git", - "reference": "d5deaecff52a0d61ccb613bb3804088da0307191" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/d5deaecff52a0d61ccb613bb3804088da0307191", - "reference": "d5deaecff52a0d61ccb613bb3804088da0307191", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "jakub-onderka/php-code-style": "1.0", - "jakub-onderka/php-parallel-lint": "1.0", - "jakub-onderka/php-var-dump-check": "0.*", - "phpunit/phpunit": "~4.3", - "squizlabs/php_codesniffer": "1.*" - }, - "type": "library", - "autoload": { - "psr-4": { - "JakubOnderka\\PhpConsoleColor\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Jakub Onderka", - "email": "jakub.onderka@gmail.com" - } - ], - "abandoned": "php-parallel-lint/php-console-color", - "time": "2018-09-29T17:23:10+00:00" - }, - { - "name": "jakub-onderka/php-console-highlighter", - "version": "v0.4", - "source": { - "type": "git", - "url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git", - "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/9f7a229a69d52506914b4bc61bfdb199d90c5547", - "reference": "9f7a229a69d52506914b4bc61bfdb199d90c5547", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "jakub-onderka/php-console-color": "~0.2", - "php": ">=5.4.0" - }, - "require-dev": { - "jakub-onderka/php-code-style": "~1.0", - "jakub-onderka/php-parallel-lint": "~1.0", - "jakub-onderka/php-var-dump-check": "~0.1", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~1.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "JakubOnderka\\PhpConsoleHighlighter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jakub Onderka", - "email": "acci@acci.cz", - "homepage": "http://www.acci.cz/" - } - ], - "description": "Highlight PHP code in terminal", - "abandoned": "php-parallel-lint/php-console-highlighter", - "time": "2018-09-29T18:48:56+00:00" - }, { "name": "laracasts/utilities", "version": "3.1", @@ -1184,49 +1214,55 @@ }, { "name": "laravel/framework", - "version": "v6.18.3", + "version": "v7.17.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "4e48acfaba87f08320a2764d36c3b6a4a4112ccf" + "reference": "6633c4017b311d3aaae842edabd567c637afc39c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/4e48acfaba87f08320a2764d36c3b6a4a4112ccf", - "reference": "4e48acfaba87f08320a2764d36c3b6a4a4112ccf", + "url": "https://api.github.com/repos/laravel/framework/zipball/6633c4017b311d3aaae842edabd567c637afc39c", + "reference": "6633c4017b311d3aaae842edabd567c637afc39c", "shasum": "" }, "require": { - "doctrine/inflector": "^1.1", + "doctrine/inflector": "^1.4|^2.0", "dragonmantank/cron-expression": "^2.0", "egulias/email-validator": "^2.1.10", "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", "league/commonmark": "^1.3", - "league/flysystem": "^1.0.8", - "monolog/monolog": "^1.12|^2.0", - "nesbot/carbon": "^2.0", + "league/flysystem": "^1.0.34", + "monolog/monolog": "^2.0", + "nesbot/carbon": "^2.17", "opis/closure": "^3.1", - "php": "^7.2", + "php": "^7.2.5", "psr/container": "^1.0", "psr/simple-cache": "^1.0", - "ramsey/uuid": "^3.7", + "ramsey/uuid": "^3.7|^4.0", "swiftmailer/swiftmailer": "^6.0", - "symfony/console": "^4.3.4", - "symfony/debug": "^4.3.4", - "symfony/finder": "^4.3.4", - "symfony/http-foundation": "^4.3.4", - "symfony/http-kernel": "^4.3.4", - "symfony/process": "^4.3.4", - "symfony/routing": "^4.3.4", - "symfony/var-dumper": "^4.3.4", - "tijsverkoyen/css-to-inline-styles": "^2.2.1", - "vlucas/phpdotenv": "^3.3" + "symfony/console": "^5.0", + "symfony/error-handler": "^5.0", + "symfony/finder": "^5.0", + "symfony/http-foundation": "^5.0", + "symfony/http-kernel": "^5.0", + "symfony/mime": "^5.0", + "symfony/polyfill-php73": "^1.17", + "symfony/process": "^5.0", + "symfony/routing": "^5.0", + "symfony/var-dumper": "^5.0", + "tijsverkoyen/css-to-inline-styles": "^2.2.2", + "vlucas/phpdotenv": "^4.0", + "voku/portable-ascii": "^1.4.8" }, "conflict": { "tightenco/collect": "<5.5.33" }, + "provide": { + "psr/container-implementation": "1.0" + }, "replace": { "illuminate/auth": "self.version", "illuminate/broadcasting": "self.version", @@ -1253,6 +1289,7 @@ "illuminate/routing": "self.version", "illuminate/session": "self.version", "illuminate/support": "self.version", + "illuminate/testing": "self.version", "illuminate/translation": "self.version", "illuminate/validation": "self.version", "illuminate/view": "self.version" @@ -1261,19 +1298,20 @@ "aws/aws-sdk-php": "^3.0", "doctrine/dbal": "^2.6", "filp/whoops": "^2.4", - "guzzlehttp/guzzle": "^6.3|^7.0", + "guzzlehttp/guzzle": "^6.3.1|^7.0", "league/flysystem-cached-adapter": "^1.0", "mockery/mockery": "^1.3.1", "moontoast/math": "^1.1", - "orchestra/testbench-core": "^4.0", + "orchestra/testbench-core": "^5.0", "pda/pheanstalk": "^4.0", - "phpunit/phpunit": "^7.5.15|^8.4|^9.0", + "phpunit/phpunit": "^8.4|^9.0", "predis/predis": "^1.1.1", - "symfony/cache": "^4.3.4" + "symfony/cache": "^5.0" }, "suggest": { "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.0).", "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6).", + "ext-ftp": "Required to use the Flysystem FTP driver.", "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", "ext-memcached": "Required to use the memcache cache driver.", "ext-pcntl": "Required to use all features of the queue worker.", @@ -1281,24 +1319,27 @@ "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", "filp/whoops": "Required for friendly error pages in development (^2.4).", "fzaninotto/faker": "Required to use the eloquent factory builder (^1.9.1).", - "guzzlehttp/guzzle": "Required to use the Mailgun mail driver and the ping methods on schedules (^6.0|^7.0).", + "guzzlehttp/guzzle": "Required to use the HTTP Client, Mailgun mail driver and the ping methods on schedules (^6.3.1|^7.0).", "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", + "mockery/mockery": "Required to use mocking (^1.3.1).", "moontoast/math": "Required to use ordered UUIDs (^1.1).", "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", + "phpunit/phpunit": "Required to use assertions and run tests (^8.4|^9.0).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0).", - "symfony/cache": "Required to PSR-6 cache bridge (^4.3.4).", - "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^1.2).", + "symfony/cache": "Required to PSR-6 cache bridge (^5.0).", + "symfony/filesystem": "Required to create relative storage directory symbolic links (^5.0).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0).", "wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "6.x-dev" + "dev-master": "7.x-dev" } }, "autoload": { @@ -1326,7 +1367,7 @@ "framework", "laravel" ], - "time": "2020-03-24T16:37:50+00:00" + "time": "2020-06-23T15:22:07+00:00" }, { "name": "laravel/helpers", @@ -1383,36 +1424,37 @@ }, { "name": "laravel/tinker", - "version": "v1.0.10", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "ad571aacbac1539c30d480908f9d0c9614eaf1a7" + "reference": "cde90a7335a2130a4488beb68f4b2141869241db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/ad571aacbac1539c30d480908f9d0c9614eaf1a7", - "reference": "ad571aacbac1539c30d480908f9d0c9614eaf1a7", + "url": "https://api.github.com/repos/laravel/tinker/zipball/cde90a7335a2130a4488beb68f4b2141869241db", + "reference": "cde90a7335a2130a4488beb68f4b2141869241db", "shasum": "" }, "require": { - "illuminate/console": "~5.1|^6.0", - "illuminate/contracts": "~5.1|^6.0", - "illuminate/support": "~5.1|^6.0", - "php": ">=5.5.9", - "psy/psysh": "0.7.*|0.8.*|0.9.*", - "symfony/var-dumper": "~3.0|~4.0" + "illuminate/console": "^6.0|^7.0|^8.0", + "illuminate/contracts": "^6.0|^7.0|^8.0", + "illuminate/support": "^6.0|^7.0|^8.0", + "php": "^7.2", + "psy/psysh": "^0.10.3", + "symfony/var-dumper": "^4.3|^5.0" }, "require-dev": { - "phpunit/phpunit": "~4.0|~5.0" + "mockery/mockery": "^1.3.1", + "phpunit/phpunit": "^8.4|^9.0" }, "suggest": { - "illuminate/database": "The Illuminate Database package (~5.1)." + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.x-dev" }, "laravel": { "providers": [ @@ -1442,20 +1484,75 @@ "laravel", "psysh" ], - "time": "2019-08-07T15:10:45+00:00" + "time": "2020-04-07T15:01:31+00:00" }, { - "name": "lcobucci/jwt", - "version": "3.3.1", + "name": "laravel/ui", + "version": "v2.0.3", "source": { "type": "git", - "url": "https://github.com/lcobucci/jwt.git", - "reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18" + "url": "https://github.com/laravel/ui.git", + "reference": "15368c5328efb7ce94f35ca750acde9b496ab1b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/a11ec5f4b4d75d1fcd04e133dede4c317aac9e18", - "reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18", + "url": "https://api.github.com/repos/laravel/ui/zipball/15368c5328efb7ce94f35ca750acde9b496ab1b1", + "reference": "15368c5328efb7ce94f35ca750acde9b496ab1b1", + "shasum": "" + }, + "require": { + "illuminate/console": "^7.0", + "illuminate/filesystem": "^7.0", + "illuminate/support": "^7.0", + "php": "^7.2.5" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^8.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Ui\\UiServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Ui\\": "src/", + "Illuminate\\Foundation\\Auth\\": "auth-backend/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel UI utilities and presets.", + "keywords": [ + "laravel", + "ui" + ], + "time": "2020-04-29T15:06:45+00:00" + }, + { + "name": "lcobucci/jwt", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/jwt.git", + "reference": "56f10808089e38623345e28af2f2d5e4eb579455" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/56f10808089e38623345e28af2f2d5e4eb579455", + "reference": "56f10808089e38623345e28af2f2d5e4eb579455", "shasum": "" }, "require": { @@ -1497,25 +1594,35 @@ "JWS", "jwt" ], - "time": "2019-05-24T18:30:49+00:00" + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2020-05-22T08:21:12+00:00" }, { "name": "league/commonmark", - "version": "1.3.2", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "75542a366ccbe1896ed79fcf3e8e68206d6c4257" + "reference": "fc33ca12575e98e57cdce7d5f38b2ca5335714b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/75542a366ccbe1896ed79fcf3e8e68206d6c4257", - "reference": "75542a366ccbe1896ed79fcf3e8e68206d6c4257", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/fc33ca12575e98e57cdce7d5f38b2ca5335714b3", + "reference": "fc33ca12575e98e57cdce7d5f38b2ca5335714b3", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": "^7.1" + "php": "^7.1 || ^8.0" }, "conflict": { "scrutinizer/ocular": "1.7.*" @@ -1528,7 +1635,7 @@ "github/gfm": "0.29.0", "michelf/php-markdown": "~1.4", "mikehaertl/php-shellcommand": "^1.4", - "phpstan/phpstan-shim": "^0.11.5", + "phpstan/phpstan": "^0.12", "phpunit/phpunit": "^7.5", "scrutinizer/ocular": "^1.5", "symfony/finder": "^4.2" @@ -1537,11 +1644,6 @@ "bin/commonmark" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, "autoload": { "psr-4": { "League\\CommonMark\\": "src" @@ -1571,20 +1673,46 @@ "md", "parser" ], - "time": "2020-03-25T19:55:28+00:00" + "funding": [ + { + "url": "https://enjoy.gitstore.app/repositories/thephpleague/commonmark", + "type": "custom" + }, + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://www.patreon.com/colinodell", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2020-06-21T20:50:13+00:00" }, { "name": "league/flysystem", - "version": "1.0.66", + "version": "1.0.69", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "021569195e15f8209b1c4bebb78bd66aa4f08c21" + "reference": "7106f78428a344bc4f643c233a94e48795f10967" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/021569195e15f8209b1c4bebb78bd66aa4f08c21", - "reference": "021569195e15f8209b1c4bebb78bd66aa4f08c21", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/7106f78428a344bc4f643c233a94e48795f10967", + "reference": "7106f78428a344bc4f643c233a94e48795f10967", "shasum": "" }, "require": { @@ -1661,20 +1789,20 @@ "type": "other" } ], - "time": "2020-03-17T18:58:12+00:00" + "time": "2020-05-18T15:13:39+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "1.0.24", + "version": "1.0.25", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "4382036bde5dc926f9b8b337e5bdb15e5ec7b570" + "reference": "d409b97a50bf85fbde30cbc9fc10237475e696ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/4382036bde5dc926f9b8b337e5bdb15e5ec7b570", - "reference": "4382036bde5dc926f9b8b337e5bdb15e5ec7b570", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/d409b97a50bf85fbde30cbc9fc10237475e696ea", + "reference": "d409b97a50bf85fbde30cbc9fc10237475e696ea", "shasum": "" }, "require": { @@ -1708,7 +1836,7 @@ } ], "description": "Flysystem adapter for the AWS S3 SDK v3.x", - "time": "2020-02-23T13:31:58+00:00" + "time": "2020-06-02T18:41:58+00:00" }, { "name": "league/flysystem-memory", @@ -1871,20 +1999,20 @@ }, { "name": "monolog/monolog", - "version": "2.0.2", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "c861fcba2ca29404dc9e617eedd9eff4616986b8" + "reference": "38914429aac460e8e4616c8cb486ecb40ec90bb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c861fcba2ca29404dc9e617eedd9eff4616986b8", - "reference": "c861fcba2ca29404dc9e617eedd9eff4616986b8", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/38914429aac460e8e4616c8cb486ecb40ec90bb1", + "reference": "38914429aac460e8e4616c8cb486ecb40ec90bb1", "shasum": "" }, "require": { - "php": "^7.2", + "php": ">=7.2", "psr/log": "^1.0.1" }, "provide": { @@ -1895,11 +2023,11 @@ "doctrine/couchdb": "~1.0@dev", "elasticsearch/elasticsearch": "^6.0", "graylog2/gelf-php": "^1.4.2", - "jakub-onderka/php-parallel-lint": "^0.9", "php-amqplib/php-amqplib": "~2.4", "php-console/php-console": "^3.1.3", + "php-parallel-lint/php-parallel-lint": "^1.0", "phpspec/prophecy": "^1.6.1", - "phpunit/phpunit": "^8.3", + "phpunit/phpunit": "^8.5", "predis/predis": "^1.1", "rollbar/rollbar": "^1.3", "ruflin/elastica": ">=0.90 <3.0", @@ -1948,7 +2076,17 @@ "logging", "psr-3" ], - "time": "2019-12-20T14:22:59+00:00" + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2020-05-22T08:12:19+00:00" }, { "name": "mtdowling/jmespath.php", @@ -2009,21 +2147,22 @@ }, { "name": "nesbot/carbon", - "version": "2.32.2", + "version": "2.35.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "f10e22cf546704fab1db4ad4b9dedbc5c797a0dc" + "reference": "4b9bd835261ef23d36397a46a76b496a458305e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/f10e22cf546704fab1db4ad4b9dedbc5c797a0dc", - "reference": "f10e22cf546704fab1db4ad4b9dedbc5c797a0dc", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4b9bd835261ef23d36397a46a76b496a458305e5", + "reference": "4b9bd835261ef23d36397a46a76b496a458305e5", "shasum": "" }, "require": { "ext-json": "*", "php": "^7.1.8 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", "symfony/translation": "^3.4 || ^4.0 || ^5.0" }, "require-dev": { @@ -2041,7 +2180,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-master": "2.x-dev", + "dev-3.x": "3.x-dev" }, "laravel": { "providers": [ @@ -2086,20 +2226,20 @@ "type": "tidelift" } ], - "time": "2020-03-31T13:43:19+00:00" + "time": "2020-05-24T18:27:52+00:00" }, { "name": "nikic/php-parser", - "version": "v4.3.0", + "version": "v4.5.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc" + "reference": "53c2753d756f5adb586dca79c2ec0e2654dd9463" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9a9981c347c5c49d6dfe5cf826bb882b824080dc", - "reference": "9a9981c347c5c49d6dfe5cf826bb882b824080dc", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/53c2753d756f5adb586dca79c2ec0e2654dd9463", + "reference": "53c2753d756f5adb586dca79c2ec0e2654dd9463", "shasum": "" }, "require": { @@ -2138,20 +2278,20 @@ "parser", "php" ], - "time": "2019-11-08T13:50:10+00:00" + "time": "2020-06-03T07:24:19+00:00" }, { "name": "opis/closure", - "version": "3.5.1", + "version": "3.5.5", "source": { "type": "git", "url": "https://github.com/opis/closure.git", - "reference": "93ebc5712cdad8d5f489b500c59d122df2e53969" + "reference": "dec9fc5ecfca93f45cd6121f8e6f14457dff372c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/93ebc5712cdad8d5f489b500c59d122df2e53969", - "reference": "93ebc5712cdad8d5f489b500c59d122df2e53969", + "url": "https://api.github.com/repos/opis/closure/zipball/dec9fc5ecfca93f45cd6121f8e6f14457dff372c", + "reference": "dec9fc5ecfca93f45cd6121f8e6f14457dff372c", "shasum": "" }, "require": { @@ -2199,7 +2339,7 @@ "serialization", "serialize" ], - "time": "2019-11-29T22:36:02+00:00" + "time": "2020-06-17T14:59:55+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -2310,16 +2450,16 @@ }, { "name": "phpoption/phpoption", - "version": "1.7.3", + "version": "1.7.4", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "4acfd6a4b33a509d8c88f50e5222f734b6aeebae" + "reference": "b2ada2ad5d8a32b89088b8adc31ecd2e3a13baf3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/4acfd6a4b33a509d8c88f50e5222f734b6aeebae", - "reference": "4acfd6a4b33a509d8c88f50e5222f734b6aeebae", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/b2ada2ad5d8a32b89088b8adc31ecd2e3a13baf3", + "reference": "b2ada2ad5d8a32b89088b8adc31ecd2e3a13baf3", "shasum": "" }, "require": { @@ -2361,7 +2501,17 @@ "php", "type" ], - "time": "2020-03-21T18:07:53+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2020-06-07T10:40:07+00:00" }, { "name": "pragmarx/google2fa", @@ -2471,16 +2621,16 @@ }, { "name": "prologue/alerts", - "version": "0.4.6", + "version": "0.4.7", "source": { "type": "git", "url": "https://github.com/prologuephp/alerts.git", - "reference": "4c621a541a7f16631deda9d1b2b075c182d251d1" + "reference": "631896b583129b2873df09b5295809c1244eddb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/prologuephp/alerts/zipball/4c621a541a7f16631deda9d1b2b075c182d251d1", - "reference": "4c621a541a7f16631deda9d1b2b075c182d251d1", + "url": "https://api.github.com/repos/prologuephp/alerts/zipball/631896b583129b2873df09b5295809c1244eddb1", + "reference": "631896b583129b2873df09b5295809c1244eddb1", "shasum": "" }, "require": { @@ -2533,7 +2683,7 @@ "laravel", "messages" ], - "time": "2020-03-03T08:33:38+00:00" + "time": "2020-04-24T06:00:16+00:00" }, { "name": "psr/container", @@ -2584,6 +2734,52 @@ ], "time": "2017-02-14T16:28:37+00:00" }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "time": "2019-01-08T18:20:26+00:00" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -2731,32 +2927,30 @@ }, { "name": "psy/psysh", - "version": "v0.9.12", + "version": "v0.10.4", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "90da7f37568aee36b116a030c5f99c915267edd4" + "reference": "a8aec1b2981ab66882a01cce36a49b6317dc3560" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/90da7f37568aee36b116a030c5f99c915267edd4", - "reference": "90da7f37568aee36b116a030c5f99c915267edd4", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/a8aec1b2981ab66882a01cce36a49b6317dc3560", + "reference": "a8aec1b2981ab66882a01cce36a49b6317dc3560", "shasum": "" }, "require": { "dnoegel/php-xdg-base-dir": "0.1.*", "ext-json": "*", "ext-tokenizer": "*", - "jakub-onderka/php-console-highlighter": "0.3.*|0.4.*", - "nikic/php-parser": "~1.3|~2.0|~3.0|~4.0", - "php": ">=5.4.0", - "symfony/console": "~2.3.10|^2.4.2|~3.0|~4.0|~5.0", - "symfony/var-dumper": "~2.7|~3.0|~4.0|~5.0" + "nikic/php-parser": "~4.0|~3.0|~2.0|~1.3", + "php": "^8.0 || ^7.0 || ^5.5.9", + "symfony/console": "~5.0|~4.0|~3.0|^2.4.2|~2.3.10", + "symfony/var-dumper": "~5.0|~4.0|~3.0|~2.7" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.2", - "hoa/console": "~2.15|~3.16", - "phpunit/phpunit": "~4.8.35|~5.0|~6.0|~7.0" + "hoa/console": "3.17.*" }, "suggest": { "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", @@ -2771,7 +2965,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-develop": "0.9.x-dev" + "dev-master": "0.10.x-dev" } }, "autoload": { @@ -2801,7 +2995,7 @@ "interactive", "shell" ], - "time": "2019-12-06T14:19:43+00:00" + "time": "2020-05-03T19:32:03+00:00" }, { "name": "ralouphie/getallheaders", @@ -2844,54 +3038,124 @@ "time": "2019-03-08T08:55:37+00:00" }, { - "name": "ramsey/uuid", - "version": "3.9.3", + "name": "ramsey/collection", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92" + "url": "https://github.com/ramsey/collection.git", + "reference": "925ad8cf55ba7a3fc92e332c58fd0478ace3e1ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/7e1633a6964b48589b142d60542f9ed31bd37a92", - "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92", + "url": "https://api.github.com/repos/ramsey/collection/zipball/925ad8cf55ba7a3fc92e332c58fd0478ace3e1ca", + "reference": "925ad8cf55ba7a3fc92e332c58fd0478ace3e1ca", "shasum": "" }, "require": { + "php": "^7.2" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0", + "fzaninotto/faker": "^1.5", + "jakub-onderka/php-parallel-lint": "^1", + "jangregor/phpstan-prophecy": "^0.6", + "mockery/mockery": "^1.3", + "phpstan/extension-installer": "^1", + "phpstan/phpdoc-parser": "0.4.1", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-mockery": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^8.5", + "slevomat/coding-standard": "^6.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP 7.2+ library for representing and manipulating collections.", + "homepage": "https://github.com/ramsey/collection", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "time": "2020-01-05T00:22:59+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "ba8fff1d3abb8bb4d35a135ed22a31c6ef3ede3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/ba8fff1d3abb8bb4d35a135ed22a31c6ef3ede3d", + "reference": "ba8fff1d3abb8bb4d35a135ed22a31c6ef3ede3d", + "shasum": "" + }, + "require": { + "brick/math": "^0.8", "ext-json": "*", - "paragonie/random_compat": "^1 | ^2 | 9.99.99", - "php": "^5.4 | ^7 | ^8", + "php": "^7.2 || ^8", + "ramsey/collection": "^1.0", "symfony/polyfill-ctype": "^1.8" }, "replace": { "rhumsaa/uuid": "self.version" }, "require-dev": { - "codeception/aspect-mock": "^1 | ^2", - "doctrine/annotations": "^1.2", - "goaop/framework": "1.0.0-alpha.2 | ^1 | ^2.1", - "jakub-onderka/php-parallel-lint": "^1", - "mockery/mockery": "^0.9.11 | ^1", + "codeception/aspect-mock": "^3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2", + "doctrine/annotations": "^1.8", + "goaop/framework": "^2", + "mockery/mockery": "^1.3", "moontoast/math": "^1.1", "paragonie/random-lib": "^2", - "php-mock/php-mock-phpunit": "^0.3 | ^1.1", - "phpunit/phpunit": "^4.8 | ^5.4 | ^6.5", - "squizlabs/php_codesniffer": "^3.5" + "php-mock/php-mock-mockery": "^1.3", + "php-mock/php-mock-phpunit": "^2.5", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpstan/extension-installer": "^1.0", + "phpstan/phpdoc-parser": "0.4.3", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-mockery": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^8.5", + "psy/psysh": "^0.10.0", + "slevomat/coding-standard": "^6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "3.9.4" }, "suggest": { - "ext-ctype": "Provides support for PHP Ctype functions", - "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", - "ext-openssl": "Provides the OpenSSL extension for use with the OpenSslGenerator", - "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", - "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-ctype": "Enables faster processing of character classification using ctype functions.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "4.x-dev" } }, "autoload": { @@ -2906,29 +3170,20 @@ "license": [ "MIT" ], - "authors": [ - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - }, - { - "name": "Marijn Huizendveld", - "email": "marijn.huizendveld@gmail.com" - }, - { - "name": "Thibaud Fabre", - "email": "thibaud@aztech.io" - } - ], - "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", "homepage": "https://github.com/ramsey/uuid", "keywords": [ "guid", "identifier", "uuid" ], - "time": "2020-02-21T04:36:14+00:00" + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + } + ], + "time": "2020-03-29T20:13:32+00:00" }, { "name": "s1lentium/iptools", @@ -3108,24 +3363,24 @@ }, { "name": "staudenmeir/belongs-to-through", - "version": "v2.9", + "version": "v2.10", "source": { "type": "git", "url": "https://github.com/staudenmeir/belongs-to-through.git", - "reference": "8f16bb7b51d081d90d9b093ba6f380f71a96d79f" + "reference": "23be043a4885f696a0e5eb24da7221947e480cb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/8f16bb7b51d081d90d9b093ba6f380f71a96d79f", - "reference": "8f16bb7b51d081d90d9b093ba6f380f71a96d79f", + "url": "https://api.github.com/repos/staudenmeir/belongs-to-through/zipball/23be043a4885f696a0e5eb24da7221947e480cb5", + "reference": "23be043a4885f696a0e5eb24da7221947e480cb5", "shasum": "" }, "require": { - "illuminate/database": "^6.0", - "php": "^7.2" + "illuminate/database": "^7.0", + "php": "^7.2.5" }, "require-dev": { - "phpunit/phpunit": "^8.0" + "phpunit/phpunit": "^8.5" }, "type": "library", "autoload": { @@ -3147,8 +3402,8 @@ "email": "mail@jonas-staudenmeir.de" } ], - "description": "Laravel Eloquent BelongsToThrough relationship", - "time": "2019-12-29T10:58:12+00:00" + "description": "Laravel Eloquent BelongsToThrough relationships", + "time": "2020-01-31T11:29:47+00:00" }, { "name": "swiftmailer/swiftmailer", @@ -3214,41 +3469,44 @@ }, { "name": "symfony/console", - "version": "v4.4.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "10bb3ee3c97308869d53b3e3d03f6ac23ff985f7" + "reference": "34ac555a3627e324b660e318daa07572e1140123" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/10bb3ee3c97308869d53b3e3d03f6ac23ff985f7", - "reference": "10bb3ee3c97308869d53b3e3d03f6ac23ff985f7", + "url": "https://api.github.com/repos/symfony/console/zipball/34ac555a3627e324b660e318daa07572e1140123", + "reference": "34ac555a3627e324b660e318daa07572e1140123", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": ">=7.2.5", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.8", - "symfony/service-contracts": "^1.1|^2" + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2", + "symfony/string": "^5.1" }, "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<4.3|>=5", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", "symfony/lock": "<4.4", - "symfony/process": "<3.3" + "symfony/process": "<4.4" }, "provide": { "psr/log-implementation": "1.0" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/event-dispatcher": "^4.3", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/event-dispatcher": "^4.4|^5.0", "symfony/lock": "^4.4|^5.0", - "symfony/process": "^3.4|^4.0|^5.0", - "symfony/var-dumper": "^4.3|^5.0" + "symfony/process": "^4.4|^5.0", + "symfony/var-dumper": "^4.4|^5.0" }, "suggest": { "psr/log": "For using the console logger", @@ -3259,7 +3517,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3300,29 +3558,29 @@ "type": "tidelift" } ], - "time": "2020-03-30T11:41:10+00:00" + "time": "2020-06-15T12:59:21+00:00" }, { "name": "symfony/css-selector", - "version": "v5.0.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "5f8d5271303dad260692ba73dfa21777d38e124e" + "reference": "e544e24472d4c97b2d11ade7caacd446727c6bf9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/5f8d5271303dad260692ba73dfa21777d38e124e", - "reference": "5f8d5271303dad260692ba73dfa21777d38e124e", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/e544e24472d4c97b2d11ade7caacd446727c6bf9", + "reference": "e544e24472d4c97b2d11ade7caacd446727c6bf9", "shasum": "" }, "require": { - "php": "^7.2.5" + "php": ">=7.2.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3367,44 +3625,34 @@ "type": "tidelift" } ], - "time": "2020-03-27T16:56:45+00:00" + "time": "2020-05-20T17:43:50+00:00" }, { - "name": "symfony/debug", - "version": "v4.4.7", + "name": "symfony/deprecation-contracts", + "version": "v2.1.2", "source": { "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "346636d2cae417992ecfd761979b2ab98b339a45" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/346636d2cae417992ecfd761979b2ab98b339a45", - "reference": "346636d2cae417992ecfd761979b2ab98b339a45", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337", + "reference": "dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337", "shasum": "" }, "require": { - "php": "^7.1.3", - "psr/log": "~1.0" - }, - "conflict": { - "symfony/http-kernel": "<3.4" - }, - "require-dev": { - "symfony/http-kernel": "^3.4|^4.0|^5.0" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "2.1-dev" } }, "autoload": { - "psr-4": { - "Symfony\\Component\\Debug\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "files": [ + "function.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3413,15 +3661,15 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Debug Component", + "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "funding": [ { @@ -3437,36 +3685,37 @@ "type": "tidelift" } ], - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-05-27T08:34:37+00:00" }, { "name": "symfony/error-handler", - "version": "v4.4.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "7e9828fc98aa1cf27b422fe478a84f5b0abb7358" + "reference": "7d0b927b9d3dc41d7d46cda38cbfcd20cdcbb896" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/7e9828fc98aa1cf27b422fe478a84f5b0abb7358", - "reference": "7e9828fc98aa1cf27b422fe478a84f5b0abb7358", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/7d0b927b9d3dc41d7d46cda38cbfcd20cdcbb896", + "reference": "7d0b927b9d3dc41d7d46cda38cbfcd20cdcbb896", "shasum": "" }, "require": { - "php": "^7.1.3", - "psr/log": "~1.0", - "symfony/debug": "^4.4.5", + "php": ">=7.2.5", + "psr/log": "^1.0", + "symfony/polyfill-php80": "^1.15", "symfony/var-dumper": "^4.4|^5.0" }, "require-dev": { + "symfony/deprecation-contracts": "^2.1", "symfony/http-kernel": "^4.4|^5.0", "symfony/serializer": "^4.4|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3507,41 +3756,43 @@ "type": "tidelift" } ], - "time": "2020-03-30T14:07:33+00:00" + "time": "2020-05-30T20:35:19+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.4.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "abc8e3618bfdb55e44c8c6a00abd333f831bbfed" + "reference": "cc0d059e2e997e79ca34125a52f3e33de4424ac7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/abc8e3618bfdb55e44c8c6a00abd333f831bbfed", - "reference": "abc8e3618bfdb55e44c8c6a00abd333f831bbfed", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/cc0d059e2e997e79ca34125a52f3e33de4424ac7", + "reference": "cc0d059e2e997e79ca34125a52f3e33de4424ac7", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/event-dispatcher-contracts": "^1.1" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/event-dispatcher-contracts": "^2", + "symfony/polyfill-php80": "^1.15" }, "conflict": { - "symfony/dependency-injection": "<3.4" + "symfony/dependency-injection": "<4.4" }, "provide": { "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "1.1" + "symfony/event-dispatcher-implementation": "2.0" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/config": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/http-foundation": "^4.4|^5.0", "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^3.4|^4.0|^5.0" + "symfony/stopwatch": "^4.4|^5.0" }, "suggest": { "symfony/dependency-injection": "", @@ -3550,7 +3801,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3591,33 +3842,33 @@ "type": "tidelift" } ], - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-05-20T17:43:50+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v1.1.7", + "version": "v2.1.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18" + "reference": "405952c4e90941a17e52ef7489a2bd94870bb290" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c43ab685673fb6c8d84220c77897b1d6cdbe1d18", - "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/405952c4e90941a17e52ef7489a2bd94870bb290", + "reference": "405952c4e90941a17e52ef7489a2bd94870bb290", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": ">=7.2.5", + "psr/event-dispatcher": "^1" }, "suggest": { - "psr/event-dispatcher": "", "symfony/event-dispatcher-implementation": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -3649,29 +3900,43 @@ "interoperability", "standards" ], - "time": "2019-09-17T09:54:03+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-20T17:43:50+00:00" }, { "name": "symfony/finder", - "version": "v4.4.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "5729f943f9854c5781984ed4907bbb817735776b" + "reference": "4298870062bfc667cb78d2b379be4bf5dec5f187" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/5729f943f9854c5781984ed4907bbb817735776b", - "reference": "5729f943f9854c5781984ed4907bbb817735776b", + "url": "https://api.github.com/repos/symfony/finder/zipball/4298870062bfc667cb78d2b379be4bf5dec5f187", + "reference": "4298870062bfc667cb78d2b379be4bf5dec5f187", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": ">=7.2.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3712,35 +3977,41 @@ "type": "tidelift" } ], - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-05-20T17:43:50+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.4.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "62f92509c9abfd1f73e17b8cf1b72c0bdac6611b" + "reference": "f93055171b847915225bd5b0a5792888419d8d75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/62f92509c9abfd1f73e17b8cf1b72c0bdac6611b", - "reference": "62f92509c9abfd1f73e17b8cf1b72c0bdac6611b", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f93055171b847915225bd5b0a5792888419d8d75", + "reference": "f93055171b847915225bd5b0a5792888419d8d75", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/mime": "^4.3|^5.0", - "symfony/polyfill-mbstring": "~1.1" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php80": "^1.15" }, "require-dev": { "predis/predis": "~1.0", - "symfony/expression-language": "^3.4|^4.0|^5.0" + "symfony/cache": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/mime": "^4.4|^5.0" + }, + "suggest": { + "symfony/mime": "To use the file extension guesser" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3781,59 +4052,68 @@ "type": "tidelift" } ], - "time": "2020-03-30T14:07:33+00:00" + "time": "2020-06-15T06:52:54+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.4.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "f356a489e51856b99908005eb7f2c51a1dfc95dc" + "reference": "a18c27ace1ef344ffcb129a5b089bad7643b387a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f356a489e51856b99908005eb7f2c51a1dfc95dc", - "reference": "f356a489e51856b99908005eb7f2c51a1dfc95dc", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/a18c27ace1ef344ffcb129a5b089bad7643b387a", + "reference": "a18c27ace1ef344ffcb129a5b089bad7643b387a", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": ">=7.2.5", "psr/log": "~1.0", - "symfony/error-handler": "^4.4", - "symfony/event-dispatcher": "^4.4", + "symfony/deprecation-contracts": "^2.1", + "symfony/error-handler": "^4.4|^5.0", + "symfony/event-dispatcher": "^5.0", "symfony/http-foundation": "^4.4|^5.0", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php73": "^1.9" + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.15" }, "conflict": { - "symfony/browser-kit": "<4.3", - "symfony/config": "<3.4", - "symfony/console": ">=5", - "symfony/dependency-injection": "<4.3", - "symfony/translation": "<4.2", - "twig/twig": "<1.34|<2.4,>=2" + "symfony/browser-kit": "<4.4", + "symfony/cache": "<5.0", + "symfony/config": "<5.0", + "symfony/console": "<4.4", + "symfony/dependency-injection": "<4.4", + "symfony/doctrine-bridge": "<5.0", + "symfony/form": "<5.0", + "symfony/http-client": "<5.0", + "symfony/mailer": "<5.0", + "symfony/messenger": "<5.0", + "symfony/translation": "<5.0", + "symfony/twig-bridge": "<5.0", + "symfony/validator": "<5.0", + "twig/twig": "<2.4" }, "provide": { "psr/log-implementation": "1.0" }, "require-dev": { "psr/cache": "~1.0", - "symfony/browser-kit": "^4.3|^5.0", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/console": "^3.4|^4.0", - "symfony/css-selector": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^4.3|^5.0", - "symfony/dom-crawler": "^3.4|^4.0|^5.0", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/finder": "^3.4|^4.0|^5.0", - "symfony/process": "^3.4|^4.0|^5.0", - "symfony/routing": "^3.4|^4.0|^5.0", - "symfony/stopwatch": "^3.4|^4.0|^5.0", - "symfony/templating": "^3.4|^4.0|^5.0", - "symfony/translation": "^4.2|^5.0", + "symfony/browser-kit": "^4.4|^5.0", + "symfony/config": "^5.0", + "symfony/console": "^4.4|^5.0", + "symfony/css-selector": "^4.4|^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/dom-crawler": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/finder": "^4.4|^5.0", + "symfony/process": "^4.4|^5.0", + "symfony/routing": "^4.4|^5.0", + "symfony/stopwatch": "^4.4|^5.0", + "symfony/translation": "^4.4|^5.0", "symfony/translation-contracts": "^1.1|^2", - "twig/twig": "^1.34|^2.4|^3.0" + "twig/twig": "^2.4|^3.0" }, "suggest": { "symfony/browser-kit": "", @@ -3844,7 +4124,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3885,26 +4165,27 @@ "type": "tidelift" } ], - "time": "2020-03-30T14:59:15+00:00" + "time": "2020-06-15T13:51:38+00:00" }, { "name": "symfony/mime", - "version": "v5.0.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "481b7d6da88922fb1e0d86a943987722b08f3955" + "reference": "c0c418f05e727606e85b482a8591519c4712cf45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/481b7d6da88922fb1e0d86a943987722b08f3955", - "reference": "481b7d6da88922fb1e0d86a943987722b08f3955", + "url": "https://api.github.com/repos/symfony/mime/zipball/c0c418f05e727606e85b482a8591519c4712cf45", + "reference": "c0c418f05e727606e85b482a8591519c4712cf45", "shasum": "" }, "require": { - "php": "^7.2.5", + "php": ">=7.2.5", "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0" + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.15" }, "conflict": { "symfony/mailer": "<4.4" @@ -3916,7 +4197,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3961,20 +4242,20 @@ "type": "tidelift" } ], - "time": "2020-03-27T16:56:45+00:00" + "time": "2020-06-09T15:07:35+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.15.0", + "version": "v1.17.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14" + "reference": "2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/4719fa9c18b0464d399f1a63bf624b42b6fa8d14", - "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d", + "reference": "2edd75b8b35d62fd3eeabba73b26b8f1f60ce13d", "shasum": "" }, "require": { @@ -3986,7 +4267,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -4033,20 +4318,20 @@ "type": "tidelift" } ], - "time": "2020-02-27T09:26:54+00:00" + "time": "2020-06-06T08:46:27+00:00" }, { "name": "symfony/polyfill-iconv", - "version": "v1.15.0", + "version": "v1.17.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "ad6d62792bfbcfc385dd34b424d4fcf9712a32c8" + "reference": "ba6c9c18db36235b859cc29b8372d1c01298c035" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/ad6d62792bfbcfc385dd34b424d4fcf9712a32c8", - "reference": "ad6d62792bfbcfc385dd34b424d4fcf9712a32c8", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/ba6c9c18db36235b859cc29b8372d1c01298c035", + "reference": "ba6c9c18db36235b859cc29b8372d1c01298c035", "shasum": "" }, "require": { @@ -4058,7 +4343,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -4106,20 +4395,98 @@ "type": "tidelift" } ], - "time": "2020-03-09T19:04:49+00:00" + "time": "2020-06-06T08:46:27+00:00" }, { - "name": "symfony/polyfill-intl-idn", - "version": "v1.15.0", + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.17.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "6e4dbcf5e81eba86e36731f94fe56b1726835846" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", - "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/6e4dbcf5e81eba86e36731f94fe56b1726835846", + "reference": "6e4dbcf5e81eba86e36731f94fe56b1726835846", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-06T08:46:27+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.17.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "a57f8161502549a742a63c09f0a604997bf47027" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a57f8161502549a742a63c09f0a604997bf47027", + "reference": "a57f8161502549a742a63c09f0a604997bf47027", "shasum": "" }, "require": { @@ -4133,7 +4500,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -4182,20 +4553,101 @@ "type": "tidelift" } ], - "time": "2020-03-09T19:04:49+00:00" + "time": "2020-06-06T08:46:27+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.15.0", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.17.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "40309d1700e8f72447bb9e7b54af756eeea35620" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/40309d1700e8f72447bb9e7b54af756eeea35620", + "reference": "40309d1700e8f72447bb9e7b54af756eeea35620", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-14T14:40:37+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.17.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "7110338d81ce1cbc3e273136e4574663627037a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7110338d81ce1cbc3e273136e4574663627037a7", + "reference": "7110338d81ce1cbc3e273136e4574663627037a7", "shasum": "" }, "require": { @@ -4207,7 +4659,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -4255,20 +4711,20 @@ "type": "tidelift" } ], - "time": "2020-03-09T19:04:49+00:00" + "time": "2020-06-06T08:46:27+00:00" }, { "name": "symfony/polyfill-php56", - "version": "v1.15.0", + "version": "v1.17.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "d51ec491c8ddceae7dca8dd6c7e30428f543f37d" + "reference": "a25861bb3c79b0ec2da9ede51de2ea573818b943" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/d51ec491c8ddceae7dca8dd6c7e30428f543f37d", - "reference": "d51ec491c8ddceae7dca8dd6c7e30428f543f37d", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/a25861bb3c79b0ec2da9ede51de2ea573818b943", + "reference": "a25861bb3c79b0ec2da9ede51de2ea573818b943", "shasum": "" }, "require": { @@ -4278,7 +4734,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -4325,20 +4785,20 @@ "type": "tidelift" } ], - "time": "2020-03-09T19:04:49+00:00" + "time": "2020-06-06T08:46:27+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "37b0976c78b94856543260ce09b460a7bc852747" + "reference": "f048e612a3905f34931127360bdd2def19a5e582" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747", - "reference": "37b0976c78b94856543260ce09b460a7bc852747", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/f048e612a3905f34931127360bdd2def19a5e582", + "reference": "f048e612a3905f34931127360bdd2def19a5e582", "shasum": "" }, "require": { @@ -4347,7 +4807,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -4394,20 +4854,20 @@ "type": "tidelift" } ], - "time": "2020-02-27T09:26:54+00:00" + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.15.0", + "version": "v1.17.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7" + "reference": "fa0837fe02d617d31fbb25f990655861bb27bd1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", - "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fa0837fe02d617d31fbb25f990655861bb27bd1a", + "reference": "fa0837fe02d617d31fbb25f990655861bb27bd1a", "shasum": "" }, "require": { @@ -4416,7 +4876,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -4466,20 +4930,100 @@ "type": "tidelift" } ], - "time": "2020-02-27T09:26:54+00:00" + "time": "2020-06-06T08:46:27+00:00" }, { - "name": "symfony/polyfill-util", - "version": "v1.15.0", + "name": "symfony/polyfill-php80", + "version": "v1.17.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-util.git", - "reference": "d8e76c104127675d0ea3df3be0f2ae24a8619027" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "4a5b6bba3259902e386eb80dd1956181ee90b5b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/d8e76c104127675d0ea3df3be0f2ae24a8619027", - "reference": "d8e76c104127675d0ea3df3be0f2ae24a8619027", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4a5b6bba3259902e386eb80dd1956181ee90b5b2", + "reference": "4a5b6bba3259902e386eb80dd1956181ee90b5b2", + "shasum": "" + }, + "require": { + "php": ">=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-06T08:46:27+00:00" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.17.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "6dd644eda43cd2f3daa883d728d8ab4120a05af6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/6dd644eda43cd2f3daa883d728d8ab4120a05af6", + "reference": "6dd644eda43cd2f3daa883d728d8ab4120a05af6", "shasum": "" }, "require": { @@ -4488,7 +5032,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -4532,29 +5080,30 @@ "type": "tidelift" } ], - "time": "2020-03-02T11:55:35+00:00" + "time": "2020-06-06T08:46:27+00:00" }, { "name": "symfony/process", - "version": "v4.4.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "3e40e87a20eaf83a1db825e1fa5097ae89042db3" + "reference": "7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/3e40e87a20eaf83a1db825e1fa5097ae89042db3", - "reference": "3e40e87a20eaf83a1db825e1fa5097ae89042db3", + "url": "https://api.github.com/repos/symfony/process/zipball/7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1", + "reference": "7f6378c1fa2147eeb1b4c385856ce9de0d46ebd1", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.15" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -4595,38 +5144,40 @@ "type": "tidelift" } ], - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-05-30T20:35:19+00:00" }, { "name": "symfony/routing", - "version": "v4.4.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "0f562fa613e288d7dbae6c63abbc9b33ed75a8f8" + "reference": "bbd0ba121d623f66d165a55a108008968911f3eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/0f562fa613e288d7dbae6c63abbc9b33ed75a8f8", - "reference": "0f562fa613e288d7dbae6c63abbc9b33ed75a8f8", + "url": "https://api.github.com/repos/symfony/routing/zipball/bbd0ba121d623f66d165a55a108008968911f3eb", + "reference": "bbd0ba121d623f66d165a55a108008968911f3eb", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-php80": "^1.15" }, "conflict": { - "symfony/config": "<4.2", - "symfony/dependency-injection": "<3.4", - "symfony/yaml": "<3.4" + "symfony/config": "<5.0", + "symfony/dependency-injection": "<4.4", + "symfony/yaml": "<4.4" }, "require-dev": { "doctrine/annotations": "~1.2", "psr/log": "~1.0", - "symfony/config": "^4.2|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/http-foundation": "^3.4|^4.0|^5.0", - "symfony/yaml": "^3.4|^4.0|^5.0" + "symfony/config": "^5.0", + "symfony/dependency-injection": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/http-foundation": "^4.4|^5.0", + "symfony/yaml": "^4.4|^5.0" }, "suggest": { "doctrine/annotations": "For using the annotation loader", @@ -4638,7 +5189,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -4685,24 +5236,24 @@ "type": "tidelift" } ], - "time": "2020-03-30T11:41:10+00:00" + "time": "2020-06-10T11:49:58+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.0.1", + "version": "v2.1.2", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "144c5e51266b281231e947b51223ba14acf1a749" + "reference": "66a8f0957a3ca54e4f724e49028ab19d75a8918b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749", - "reference": "144c5e51266b281231e947b51223ba14acf1a749", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/66a8f0957a3ca54e4f724e49028ab19d75a8918b", + "reference": "66a8f0957a3ca54e4f724e49028ab19d75a8918b", "shasum": "" }, "require": { - "php": "^7.2.5", + "php": ">=7.2.5", "psr/container": "^1.0" }, "suggest": { @@ -4711,7 +5262,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -4743,46 +5294,147 @@ "interoperability", "standards" ], - "time": "2019-11-18T17:27:11+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-20T17:43:50+00:00" }, { - "name": "symfony/translation", - "version": "v4.4.7", + "name": "symfony/string", + "version": "v5.1.2", "source": { "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "4e54d336f2eca5facad449d0b0118bb449375b76" + "url": "https://github.com/symfony/string.git", + "reference": "ac70459db781108db7c6d8981dd31ce0e29e3298" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/4e54d336f2eca5facad449d0b0118bb449375b76", - "reference": "4e54d336f2eca5facad449d0b0118bb449375b76", + "url": "https://api.github.com/repos/symfony/string/zipball/ac70459db781108db7c6d8981dd31ce0e29e3298", + "reference": "ac70459db781108db7c6d8981dd31ce0e29e3298", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^1.1.6|^2" + "symfony/polyfill-php80": "~1.15" + }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "files": [ + "Resources/functions.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony String component", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-11T12:16:36+00:00" + }, + { + "name": "symfony/translation", + "version": "v5.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "d387f07d4c15f9c09439cf3f13ddbe0b2c5e8be2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/d387f07d4c15f9c09439cf3f13ddbe0b2c5e8be2", + "reference": "d387f07d4c15f9c09439cf3f13ddbe0b2c5e8be2", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "^1.15", + "symfony/translation-contracts": "^2" }, "conflict": { - "symfony/config": "<3.4", - "symfony/dependency-injection": "<3.4", - "symfony/http-kernel": "<4.4", - "symfony/yaml": "<3.4" + "symfony/config": "<4.4", + "symfony/dependency-injection": "<5.0", + "symfony/http-kernel": "<5.0", + "symfony/twig-bundle": "<5.0", + "symfony/yaml": "<4.4" }, "provide": { - "symfony/translation-implementation": "1.0" + "symfony/translation-implementation": "2.0" }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/console": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/finder": "~2.8|~3.0|~4.0|^5.0", - "symfony/http-kernel": "^4.4", - "symfony/intl": "^3.4|^4.0|^5.0", + "symfony/config": "^4.4|^5.0", + "symfony/console": "^4.4|^5.0", + "symfony/dependency-injection": "^5.0", + "symfony/finder": "^4.4|^5.0", + "symfony/http-kernel": "^5.0", + "symfony/intl": "^4.4|^5.0", "symfony/service-contracts": "^1.1.2|^2", - "symfony/yaml": "^3.4|^4.0|^5.0" + "symfony/yaml": "^4.4|^5.0" }, "suggest": { "psr/log-implementation": "To use logging capability in translator", @@ -4792,7 +5444,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -4833,24 +5485,24 @@ "type": "tidelift" } ], - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-05-30T20:35:19+00:00" }, { "name": "symfony/translation-contracts", - "version": "v2.0.1", + "version": "v2.1.2", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed" + "reference": "e5ca07c8f817f865f618aa072c2fe8e0e637340e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/8cc682ac458d75557203b2f2f14b0b92e1c744ed", - "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/e5ca07c8f817f865f618aa072c2fe8e0e637340e", + "reference": "e5ca07c8f817f865f618aa072c2fe8e0e637340e", "shasum": "" }, "require": { - "php": "^7.2.5" + "php": ">=7.2.5" }, "suggest": { "symfony/translation-implementation": "" @@ -4858,7 +5510,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -4890,36 +5542,50 @@ "interoperability", "standards" ], - "time": "2019-11-18T17:27:11+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-20T17:43:50+00:00" }, { "name": "symfony/var-dumper", - "version": "v4.4.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "5a0c2d93006131a36cf6f767d10e2ca8333b0d4a" + "reference": "46a942903059b0b05e601f00eb64179e05578c0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/5a0c2d93006131a36cf6f767d10e2ca8333b0d4a", - "reference": "5a0c2d93006131a36cf6f767d10e2ca8333b0d4a", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/46a942903059b0b05e601f00eb64179e05578c0f", + "reference": "46a942903059b0b05e601f00eb64179e05578c0f", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": ">=7.2.5", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php72": "~1.5" + "symfony/polyfill-php80": "^1.15" }, "conflict": { - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", - "symfony/console": "<3.4" + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<4.4" }, "require-dev": { "ext-iconv": "*", - "symfony/console": "^3.4|^4.0|^5.0", + "symfony/console": "^4.4|^5.0", "symfony/process": "^4.4|^5.0", - "twig/twig": "^1.34|^2.4|^3.0" + "twig/twig": "^2.4|^3.0" }, "suggest": { "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", @@ -4932,7 +5598,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -4980,24 +5646,24 @@ "type": "tidelift" } ], - "time": "2020-03-27T16:54:36+00:00" + "time": "2020-05-30T20:35:19+00:00" }, { "name": "symfony/yaml", - "version": "v4.4.7", + "version": "v4.4.10", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "ef166890d821518106da3560086bfcbeb4fadfec" + "reference": "c2d2cc66e892322cfcc03f8f12f8340dbd7a3f8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/ef166890d821518106da3560086bfcbeb4fadfec", - "reference": "ef166890d821518106da3560086bfcbeb4fadfec", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c2d2cc66e892322cfcc03f8f12f8340dbd7a3f8a", + "reference": "c2d2cc66e892322cfcc03f8f12f8340dbd7a3f8a", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": ">=7.1.3", "symfony/polyfill-ctype": "~1.8" }, "conflict": { @@ -5053,7 +5719,7 @@ "type": "tidelift" } ], - "time": "2020-03-30T11:41:10+00:00" + "time": "2020-05-20T08:37:50+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -5106,27 +5772,28 @@ }, { "name": "vlucas/phpdotenv", - "version": "v3.6.2", + "version": "v4.1.7", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "786a947e57086cf236cefdee80784634224b99fa" + "reference": "db63b2ea280fdcf13c4ca392121b0b2450b51193" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/786a947e57086cf236cefdee80784634224b99fa", - "reference": "786a947e57086cf236cefdee80784634224b99fa", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/db63b2ea280fdcf13c4ca392121b0b2450b51193", + "reference": "db63b2ea280fdcf13c4ca392121b0b2450b51193", "shasum": "" }, "require": { - "php": "^5.4 || ^7.0", - "phpoption/phpoption": "^1.5", - "symfony/polyfill-ctype": "^1.9" + "php": "^5.5.9 || ^7.0 || ^8.0", + "phpoption/phpoption": "^1.7.3", + "symfony/polyfill-ctype": "^1.16" }, "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", "ext-filter": "*", "ext-pcre": "*", - "phpunit/phpunit": "^4.8.35 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.8.35 || ^5.7.27 || ^6.5.6 || ^7.0" }, "suggest": { "ext-filter": "Required to use the boolean validator.", @@ -5135,7 +5802,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.6-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -5166,25 +5833,95 @@ "environment" ], "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", "type": "tidelift" } ], - "time": "2020-03-27T23:36:02+00:00" + "time": "2020-06-07T18:25:35+00:00" }, { - "name": "webmozart/assert", - "version": "1.7.0", + "name": "voku/portable-ascii", + "version": "1.5.2", "source": { "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "aed98a490f9a8f78468232db345ab9cf606cf598" + "url": "https://github.com/voku/portable-ascii.git", + "reference": "618631dc601d8eb6ea0a9fbf654ec82f066c4e97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/aed98a490f9a8f78468232db345ab9cf606cf598", - "reference": "aed98a490f9a8f78468232db345ab9cf606cf598", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/618631dc601d8eb6ea0a9fbf654ec82f066c4e97", + "reference": "618631dc601d8eb6ea0a9fbf654ec82f066c4e97", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "http://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2020-06-15T23:49:30+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "9dc4f203e36f2b486149058bade43c851dd97451" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/9dc4f203e36f2b486149058bade43c851dd97451", + "reference": "9dc4f203e36f2b486149058bade43c851dd97451", "shasum": "" }, "require": { @@ -5192,7 +5929,8 @@ "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "vimeo/psalm": "<3.6.0" + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" }, "require-dev": { "phpunit/phpunit": "^4.8.36 || ^7.5.13" @@ -5219,7 +5957,7 @@ "check", "validate" ], - "time": "2020-02-14T12:15:55+00:00" + "time": "2020-06-16T10:16:42+00:00" } ], "packages-dev": [ @@ -5270,16 +6008,16 @@ }, { "name": "barryvdh/laravel-debugbar", - "version": "v3.2.9", + "version": "v3.3.3", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "42d5da5379a7860093f8e4032167e4cb5ebec180" + "reference": "57f2219f6d9efe41ed1bc880d86701c52f261bf5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/42d5da5379a7860093f8e4032167e4cb5ebec180", - "reference": "42d5da5379a7860093f8e4032167e4cb5ebec180", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/57f2219f6d9efe41ed1bc880d86701c52f261bf5", + "reference": "57f2219f6d9efe41ed1bc880d86701c52f261bf5", "shasum": "" }, "require": { @@ -5334,20 +6072,26 @@ "profiler", "webprofiler" ], - "time": "2020-02-25T20:42:23+00:00" + "funding": [ + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2020-05-05T10:53:32+00:00" }, { "name": "barryvdh/laravel-ide-helper", - "version": "v2.6.7", + "version": "v2.7.0", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "edd69c5e0508972c81f1f7173236de2459c45814" + "reference": "5f677edc14bdcfdcac36633e6eea71b2728a4dbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/edd69c5e0508972c81f1f7173236de2459c45814", - "reference": "edd69c5e0508972c81f1f7173236de2459c45814", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/5f677edc14bdcfdcac36633e6eea71b2728a4dbc", + "reference": "5f677edc14bdcfdcac36633e6eea71b2728a4dbc", "shasum": "" }, "require": { @@ -5363,7 +6107,7 @@ "illuminate/config": "^5.5|^6|^7", "illuminate/view": "^5.5|^6|^7", "mockery/mockery": "^1.3", - "orchestra/testbench": "^3|^4", + "orchestra/testbench": "^3|^4|^5", "phpro/grumphp": "^0.17.1", "squizlabs/php_codesniffer": "^3" }, @@ -5405,7 +6149,13 @@ "phpstorm", "sublime" ], - "time": "2020-02-25T20:41:32+00:00" + "funding": [ + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2020-04-22T09:57:26+00:00" }, { "name": "barryvdh/reflection-docblock", @@ -5555,16 +6305,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.2.6", + "version": "1.2.7", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e" + "reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/47fe531de31fca4a1b997f87308e7d7804348f7e", - "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/95c63ab2117a72f48f5a55da9740a3273d45b7fd", + "reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd", "shasum": "" }, "require": { @@ -5607,20 +6357,30 @@ "ssl", "tls" ], - "time": "2020-01-13T10:02:55+00:00" + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-04-08T08:27:21+00:00" }, { "name": "composer/composer", - "version": "1.10.1", + "version": "1.10.7", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "b912a45da3e2b22f5cb5a23e441b697a295ba011" + "reference": "956608ea4f7de9e58c53dfb019d85ae62b193c39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/b912a45da3e2b22f5cb5a23e441b697a295ba011", - "reference": "b912a45da3e2b22f5cb5a23e441b697a295ba011", + "url": "https://api.github.com/repos/composer/composer/zipball/956608ea4f7de9e58c53dfb019d85ae62b193c39", + "reference": "956608ea4f7de9e58c53dfb019d85ae62b193c39", "shasum": "" }, "require": { @@ -5628,7 +6388,7 @@ "composer/semver": "^1.0", "composer/spdx-licenses": "^1.2", "composer/xdebug-handler": "^1.1", - "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0", + "justinrainbow/json-schema": "^5.2.10", "php": "^5.3.2 || ^7.0", "psr/log": "^1.0", "seld/jsonlint": "^1.4", @@ -5639,7 +6399,8 @@ "symfony/process": "^2.7 || ^3.0 || ^4.0 || ^5.0" }, "conflict": { - "symfony/console": "2.8.38" + "symfony/console": "2.8.38", + "symfony/phpunit-bridge": "3.4.40" }, "require-dev": { "phpspec/prophecy": "^1.10", @@ -5692,12 +6453,16 @@ "url": "https://packagist.com", "type": "custom" }, + { + "url": "https://github.com/composer", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/composer/composer", "type": "tidelift" } ], - "time": "2020-03-13T19:34:27+00:00" + "time": "2020-06-03T08:03:56+00:00" }, { "name": "composer/semver", @@ -5822,16 +6587,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.4.1", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "1ab9842d69e64fb3a01be6b656501032d1b78cb7" + "reference": "fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/1ab9842d69e64fb3a01be6b656501032d1b78cb7", - "reference": "1ab9842d69e64fb3a01be6b656501032d1b78cb7", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51", + "reference": "fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51", "shasum": "" }, "require": { @@ -5866,28 +6631,36 @@ { "url": "https://packagist.com", "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" } ], - "time": "2020-03-01T12:26:26+00:00" + "time": "2020-06-04T11:16:35+00:00" }, { "name": "doctrine/annotations", - "version": "1.10.1", + "version": "1.10.3", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "5eb79f3dbdffed6544e1fc287572c0f462bd29bb" + "reference": "5db60a4969eba0e0c197a19c077780aadbc43c5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/5eb79f3dbdffed6544e1fc287572c0f462bd29bb", - "reference": "5eb79f3dbdffed6544e1fc287572c0f462bd29bb", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/5db60a4969eba0e0c197a19c077780aadbc43c5d", + "reference": "5db60a4969eba0e0c197a19c077780aadbc43c5d", "shasum": "" }, "require": { "doctrine/lexer": "1.*", "ext-tokenizer": "*", - "php": "^7.1" + "php": "^7.1 || ^8.0" }, "require-dev": { "doctrine/cache": "1.*", @@ -5937,24 +6710,24 @@ "docblock", "parser" ], - "time": "2020-04-02T12:33:25+00:00" + "time": "2020-05-25T17:24:27+00:00" }, { "name": "doctrine/instantiator", - "version": "1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "ae466f726242e637cebdd526a7d991b9433bacf1" + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1", - "reference": "ae466f726242e637cebdd526a7d991b9433bacf1", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.1 || ^8.0" }, "require-dev": { "doctrine/coding-standard": "^6.0", @@ -5993,7 +6766,21 @@ "constructor", "instantiate" ], - "time": "2019-10-21T16:45:58+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-05-29T17:27:14+00:00" }, { "name": "friendsofphp/php-cs-fixer", @@ -6143,16 +6930,16 @@ }, { "name": "justinrainbow/json-schema", - "version": "5.2.9", + "version": "5.2.10", "source": { "type": "git", "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "44c6787311242a979fa15c704327c20e7221a0e4" + "reference": "2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/44c6787311242a979fa15c704327c20e7221a0e4", - "reference": "44c6787311242a979fa15c704327c20e7221a0e4", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b", + "reference": "2ba9c8c862ecd5510ed16c6340aa9f6eadb4f31b", "shasum": "" }, "require": { @@ -6205,38 +6992,38 @@ "json", "schema" ], - "time": "2019-09-25T14:49:45+00:00" + "time": "2020-05-27T16:41:55+00:00" }, { "name": "laravel/dusk", - "version": "v5.11.0", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/laravel/dusk.git", - "reference": "e07cc46a1e39767739e8197189780b4c2639806d" + "reference": "5481bfd50c80599d26529b7f2c9adeb2154a57fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/dusk/zipball/e07cc46a1e39767739e8197189780b4c2639806d", - "reference": "e07cc46a1e39767739e8197189780b4c2639806d", + "url": "https://api.github.com/repos/laravel/dusk/zipball/5481bfd50c80599d26529b7f2c9adeb2154a57fc", + "reference": "5481bfd50c80599d26529b7f2c9adeb2154a57fc", "shasum": "" }, "require": { "ext-json": "*", "ext-zip": "*", - "illuminate/console": "~5.7.0|~5.8.0|^6.0|^7.0", - "illuminate/support": "~5.7.0|~5.8.0|^6.0|^7.0", - "nesbot/carbon": "^1.20|^2.0", - "php": ">=7.1.0", + "illuminate/console": "^6.0|^7.0", + "illuminate/support": "^6.0|^7.0", + "nesbot/carbon": "^2.0", + "php": "^7.2", "php-webdriver/webdriver": "^1.8.1", - "symfony/console": "^4.0|^5.0", - "symfony/finder": "^4.0|^5.0", - "symfony/process": "^4.0|^5.0", - "vlucas/phpdotenv": "^2.2|^3.0|^4.0" + "symfony/console": "^4.3|^5.0", + "symfony/finder": "^4.3|^5.0", + "symfony/process": "^4.3|^5.0", + "vlucas/phpdotenv": "^3.0|^4.0" }, "require-dev": { "mockery/mockery": "^1.0", - "phpunit/phpunit": "^7.5|^8.0" + "phpunit/phpunit": "^7.5.15|^8.4|^9.0" }, "suggest": { "ext-pcntl": "Used to gracefully terminate Dusk when tests are running." @@ -6244,7 +7031,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "6.x-dev" }, "laravel": { "providers": [ @@ -6273,20 +7060,20 @@ "testing", "webdriver" ], - "time": "2020-03-24T16:21:49+00:00" + "time": "2020-06-16T19:05:20+00:00" }, { "name": "maximebf/debugbar", - "version": "v1.16.1", + "version": "v1.16.3", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "58998b818c6567fac01e35b8a4b70c1a64530556" + "reference": "1a1605b8e9bacb34cc0c6278206d699772e1d372" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/58998b818c6567fac01e35b8a4b70c1a64530556", - "reference": "58998b818c6567fac01e35b8a4b70c1a64530556", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/1a1605b8e9bacb34cc0c6278206d699772e1d372", + "reference": "1a1605b8e9bacb34cc0c6278206d699772e1d372", "shasum": "" }, "require": { @@ -6334,7 +7121,7 @@ "debug", "debugbar" ], - "time": "2019-11-24T09:46:11+00:00" + "time": "2020-05-06T07:06:27+00:00" }, { "name": "myclabs/deep-copy", @@ -6539,16 +7326,16 @@ }, { "name": "php-mock/php-mock", - "version": "2.2.1", + "version": "2.2.2", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock.git", - "reference": "8ca7205ad5e73fbbffa9bde9f6bc90daf5e49702" + "reference": "890d3e32e3a5f29715a8fd17debd87a0c9e614a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock/zipball/8ca7205ad5e73fbbffa9bde9f6bc90daf5e49702", - "reference": "8ca7205ad5e73fbbffa9bde9f6bc90daf5e49702", + "url": "https://api.github.com/repos/php-mock/php-mock/zipball/890d3e32e3a5f29715a8fd17debd87a0c9e614a0", + "reference": "890d3e32e3a5f29715a8fd17debd87a0c9e614a0", "shasum": "" }, "require": { @@ -6599,7 +7386,7 @@ "test", "test double" ], - "time": "2020-02-08T14:50:32+00:00" + "time": "2020-04-17T16:39:00+00:00" }, { "name": "php-mock/php-mock-integration", @@ -6775,24 +7562,21 @@ }, { "name": "phpdocumentor/reflection-common", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" + "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/6568f4687e5b41b054365f9ae03fcb1ed5f2069b", + "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b", "shasum": "" }, "require": { "php": ">=7.1" }, - "require-dev": { - "phpunit/phpunit": "~6" - }, "type": "library", "extra": { "branch-alias": { @@ -6823,7 +7607,7 @@ "reflection", "static analysis" ], - "time": "2018-08-07T13:53:10+00:00" + "time": "2020-04-27T09:25:28+00:00" }, { "name": "phpdocumentor/reflection-docblock", @@ -6880,16 +7664,16 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "7462d5f123dfc080dfdf26897032a6513644fc95" + "reference": "30441f2752e493c639526b215ed81d54f369d693" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/7462d5f123dfc080dfdf26897032a6513644fc95", - "reference": "7462d5f123dfc080dfdf26897032a6513644fc95", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/30441f2752e493c639526b215ed81d54f369d693", + "reference": "30441f2752e493c639526b215ed81d54f369d693", "shasum": "" }, "require": { @@ -6903,7 +7687,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-1.x": "1.x-dev" } }, "autoload": { @@ -6922,7 +7706,7 @@ } ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "time": "2020-02-18T18:59:58+00:00" + "time": "2020-06-19T20:22:09+00:00" }, { "name": "phpspec/prophecy", @@ -6989,40 +7773,40 @@ }, { "name": "phpunit/php-code-coverage", - "version": "6.1.4", + "version": "7.0.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" + "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", - "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f1884187926fbb755a9aaf0b3836ad3165b478bf", + "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^7.1", - "phpunit/php-file-iterator": "^2.0", + "php": "^7.2", + "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.0", + "phpunit/php-token-stream": "^3.1.1", "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.1 || ^4.0", + "sebastian/environment": "^4.2.2", "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1" + "theseer/tokenizer": "^1.1.3" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^8.2.2" }, "suggest": { - "ext-xdebug": "^2.6.0" + "ext-xdebug": "^2.7.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "6.1-dev" + "dev-master": "7.0-dev" } }, "autoload": { @@ -7048,7 +7832,7 @@ "testing", "xunit" ], - "time": "2018-10-31T16:06:48+00:00" + "time": "2019-11-20T13:55:58+00:00" }, { "name": "phpunit/php-file-iterator", @@ -7241,53 +8025,52 @@ }, { "name": "phpunit/phpunit", - "version": "7.5.20", + "version": "8.5.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "9467db479d1b0487c99733bb1e7944d32deded2c" + "reference": "34c18baa6a44f1d1fbf0338907139e9dce95b997" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c", - "reference": "9467db479d1b0487c99733bb1e7944d32deded2c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/34c18baa6a44f1d1fbf0338907139e9dce95b997", + "reference": "34c18baa6a44f1d1fbf0338907139e9dce95b997", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.1", + "doctrine/instantiator": "^1.2.0", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "^1.7", - "phar-io/manifest": "^1.0.2", - "phar-io/version": "^2.0", - "php": "^7.1", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^6.0.7", - "phpunit/php-file-iterator": "^2.0.1", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.9.1", + "phar-io/manifest": "^1.0.3", + "phar-io/version": "^2.0.1", + "php": "^7.2", + "phpspec/prophecy": "^1.8.1", + "phpunit/php-code-coverage": "^7.0.7", + "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1", - "sebastian/comparator": "^3.0", - "sebastian/diff": "^3.0", - "sebastian/environment": "^4.0", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^2.0", + "phpunit/php-timer": "^2.1.2", + "sebastian/comparator": "^3.0.2", + "sebastian/diff": "^3.0.2", + "sebastian/environment": "^4.2.2", + "sebastian/exporter": "^3.1.1", + "sebastian/global-state": "^3.0.0", "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0", + "sebastian/resource-operations": "^2.0.1", + "sebastian/type": "^1.1.3", "sebastian/version": "^2.0.1" }, - "conflict": { - "phpunit/phpunit-mock-objects": "*" - }, "require-dev": { "ext-pdo": "*" }, "suggest": { "ext-soap": "*", "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0" + "phpunit/php-invoker": "^2.0.0" }, "bin": [ "phpunit" @@ -7295,7 +8078,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.5-dev" + "dev-master": "8.5-dev" } }, "autoload": { @@ -7321,7 +8104,17 @@ "testing", "xunit" ], - "time": "2020-01-08T08:45:45+00:00" + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-22T07:06:58+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -7610,23 +8403,26 @@ }, { "name": "sebastian/global-state", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", + "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.2", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "ext-dom": "*", + "phpunit/phpunit": "^8.0" }, "suggest": { "ext-uopz": "*" @@ -7634,7 +8430,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -7657,7 +8453,7 @@ "keywords": [ "global state" ], - "time": "2017-04-27T15:39:26+00:00" + "time": "2019-02-01T05:30:01+00:00" }, { "name": "sebastian/object-enumerator", @@ -7846,6 +8642,52 @@ "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "time": "2018-10-04T04:07:39+00:00" }, + { + "name": "sebastian/type", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/3aaaa15fa71d27650d62a948be022fe3b48541a3", + "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3", + "shasum": "" + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "time": "2019-07-02T08:10:15+00:00" + }, { "name": "sebastian/version", "version": "2.0.1", @@ -7891,20 +8733,20 @@ }, { "name": "seld/jsonlint", - "version": "1.7.2", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19" + "reference": "ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/e2e5d290e4d2a4f0eb449f510071392e00e10d19", - "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1", + "reference": "ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1", "shasum": "" }, "require": { - "php": "^5.3 || ^7.0" + "php": "^5.3 || ^7.0 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" @@ -7936,7 +8778,17 @@ "parser", "validator" ], - "time": "2019-10-24T14:27:39+00:00" + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2020-04-30T19:05:18+00:00" }, { "name": "seld/phar-utils", @@ -7983,27 +8835,98 @@ "time": "2020-02-14T15:25:33+00:00" }, { - "name": "symfony/filesystem", - "version": "v5.0.7", + "name": "symfony/debug", + "version": "v4.4.10", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "ca3b87dd09fff9b771731637f5379965fbfab420" + "url": "https://github.com/symfony/debug.git", + "reference": "28f92d08bb6d1fddf8158e02c194ad43870007e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/ca3b87dd09fff9b771731637f5379965fbfab420", - "reference": "ca3b87dd09fff9b771731637f5379965fbfab420", + "url": "https://api.github.com/repos/symfony/debug/zipball/28f92d08bb6d1fddf8158e02c194ad43870007e6", + "reference": "28f92d08bb6d1fddf8158e02c194ad43870007e6", "shasum": "" }, "require": { - "php": "^7.2.5", + "php": ">=7.1.3", + "psr/log": "~1.0", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "symfony/http-kernel": "<3.4" + }, + "require-dev": { + "symfony/http-kernel": "^3.4|^4.0|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-24T08:33:35+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v5.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "6e4320f06d5f2cce0d96530162491f4465179157" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/6e4320f06d5f2cce0d96530162491f4465179157", + "reference": "6e4320f06d5f2cce0d96530162491f4465179157", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", "symfony/polyfill-ctype": "~1.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -8044,29 +8967,31 @@ "type": "tidelift" } ], - "time": "2020-03-27T16:56:45+00:00" + "time": "2020-05-30T20:35:19+00:00" }, { "name": "symfony/options-resolver", - "version": "v5.0.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "09dccfffd24b311df7f184aa80ee7b61ad61ed8d" + "reference": "663f5dd5e14057d1954fe721f9709d35837f2447" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/09dccfffd24b311df7f184aa80ee7b61ad61ed8d", - "reference": "09dccfffd24b311df7f184aa80ee7b61ad61ed8d", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/663f5dd5e14057d1954fe721f9709d35837f2447", + "reference": "663f5dd5e14057d1954fe721f9709d35837f2447", "shasum": "" }, "require": { - "php": "^7.2.5" + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-php80": "^1.15" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -8112,20 +9037,20 @@ "type": "tidelift" } ], - "time": "2020-03-27T16:56:45+00:00" + "time": "2020-05-23T13:08:13+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.15.0", + "version": "v1.17.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "2a18e37a489803559284416df58c71ccebe50bf0" + "reference": "471b096aede7025bace8eb356b9ac801aaba7e2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/2a18e37a489803559284416df58c71ccebe50bf0", - "reference": "2a18e37a489803559284416df58c71ccebe50bf0", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/471b096aede7025bace8eb356b9ac801aaba7e2d", + "reference": "471b096aede7025bace8eb356b9ac801aaba7e2d", "shasum": "" }, "require": { @@ -8135,7 +9060,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { @@ -8171,30 +9100,44 @@ "portable", "shim" ], - "time": "2020-02-27T09:26:54+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-06-06T08:46:27+00:00" }, { "name": "symfony/stopwatch", - "version": "v5.0.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "a1d86d30d4522423afc998f32404efa34fcf5a73" + "reference": "0f7c58cf81dbb5dd67d423a89d577524a2ec0323" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/a1d86d30d4522423afc998f32404efa34fcf5a73", - "reference": "a1d86d30d4522423afc998f32404efa34fcf5a73", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/0f7c58cf81dbb5dd67d423a89d577524a2ec0323", + "reference": "0f7c58cf81dbb5dd67d423a89d577524a2ec0323", "shasum": "" }, "require": { - "php": "^7.2.5", + "php": ">=7.2.5", "symfony/service-contracts": "^1.0|^2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -8235,7 +9178,7 @@ "type": "tidelift" } ], - "time": "2020-03-27T16:56:45+00:00" + "time": "2020-05-20T17:43:50+00:00" }, { "name": "theseer/tokenizer", @@ -8284,7 +9227,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": ">=7.2", + "php": "^7.2", "ext-mbstring": "*", "ext-pdo_mysql": "*", "ext-zip": "*" diff --git a/tests/Unit/Commands/Server/BulkPowerActionCommandTest.php b/tests/Unit/Commands/Server/BulkPowerActionCommandTest.php index 7bac15d70..3c760fcb7 100644 --- a/tests/Unit/Commands/Server/BulkPowerActionCommandTest.php +++ b/tests/Unit/Commands/Server/BulkPowerActionCommandTest.php @@ -8,19 +8,19 @@ use GuzzleHttp\Psr7\Response; use Pterodactyl\Models\Server; use Illuminate\Validation\Factory; use Tests\Unit\Commands\CommandTestCase; +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 +31,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,10 +47,7 @@ class BulkPowerActionCommandTest extends CommandTestCase $server->setRelation('node', factory(Node::class)->make()); } - $this->repository->shouldReceive('getServersForPowerActionCount') - ->once() - ->with([], []) - ->andReturn(2); + $this->repository->expects('getServersForPowerActionCount')->with([], [])->andReturns(2); $this->repository->shouldReceive('getServersForPowerAction') ->once() From 86bc9da893def568428d19ce378eeda47de9a136 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 23 Jun 2020 21:25:44 -0700 Subject: [PATCH 24/63] Add back some dev deps --- composer.json | 2 + composer.lock | 168 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 169 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b8de45ca0..5c85ecdf4 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,9 @@ "barryvdh/laravel-ide-helper": "^2.7", "codedungeon/phpunit-result-printer": "0.25.1", "friendsofphp/php-cs-fixer": "2.16.1", + "fzaninotto/faker": "^1.9", "laravel/dusk": "^6.3", + "mockery/mockery": "^1.4", "php-mock/php-mock-phpunit": "^2.6", "phpunit/phpunit": "^8.5" }, diff --git a/composer.lock b/composer.lock index ad2234816..0ddb8d227 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "394f932491c6133156e0a887ca7eebfb", + "content-hash": "79af09c27859a87b05f58bde40a29576", "packages": [ { "name": "appstract/laravel-blade-directives", @@ -6871,6 +6871,104 @@ "description": "A tool to automatically fix PHP code style", "time": "2019-11-25T22:10:32+00:00" }, + { + "name": "fzaninotto/faker", + "version": "v1.9.1", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/fc10d778e4b84d5bd315dad194661e091d307c6f", + "reference": "fc10d778e4b84d5bd315dad194661e091d307c6f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7", + "squizlabs/php_codesniffer": "^2.9.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "time": "2019-12-12T13:22:17+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/776503d3a8e85d4f9a1148614f95b7a608b046ad", + "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "1.3.3", + "phpunit/phpunit": "~4.0", + "satooshi/php-coveralls": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "time": "2016-01-20T08:20:44+00:00" + }, { "name": "hassankhan/config", "version": "0.11.2", @@ -7123,6 +7221,74 @@ ], "time": "2020-05-06T07:06:27+00:00" }, + { + "name": "mockery/mockery", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "6c6a7c533469873deacf998237e7649fc6b36223" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/6c6a7c533469873deacf998237e7649fc6b36223", + "reference": "6c6a7c533469873deacf998237e7649fc6b36223", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "~2.0", + "lib-pcre": ">=7.0", + "php": "^7.3.0" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.0.0 || ^9.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "time": "2020-05-19T14:25:16+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.9.5", From eaae74fe33269e19e3f1c10a56220fdfeeeffb78 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 23 Jun 2020 21:33:56 -0700 Subject: [PATCH 25/63] Fix immediately obvious deprecation notices while running command tests --- .../Environment/EmailSettingsCommandTest.php | 25 ++++------ .../Location/DeleteLocationCommandTest.php | 10 ++-- .../Location/MakeLocationCommandTest.php | 4 +- .../CleanServiceBackupFilesCommandTest.php | 2 +- .../Schedule/ProcessRunnableCommandTest.php | 13 ++---- .../Server/BulkPowerActionCommandTest.php | 46 ++++++++----------- .../Commands/User/DeleteUserCommandTest.php | 18 ++++---- .../User/DisableTwoFactorCommandTest.php | 4 +- .../Commands/User/MakeUserCommandTest.php | 26 +++++------ 9 files changed, 63 insertions(+), 85 deletions(-) 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 3c760fcb7..d1ba90cf4 100644 --- a/tests/Unit/Commands/Server/BulkPowerActionCommandTest.php +++ b/tests/Unit/Commands/Server/BulkPowerActionCommandTest.php @@ -6,8 +6,10 @@ 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; @@ -47,25 +49,18 @@ class BulkPowerActionCommandTest extends CommandTestCase $server->setRelation('node', factory(Node::class)->make()); } - $this->repository->expects('getServersForPowerActionCount')->with([], [])->andReturns(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); } /** @@ -76,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', @@ -97,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); } /** @@ -109,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', @@ -124,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); } /** @@ -134,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); } } From 536180ed0c30c3e0da161b6344b819be3ca1aeb6 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 23 Jun 2020 21:59:37 -0700 Subject: [PATCH 26/63] Return Http test cases to a passing state --- .phpunit.result.cache | 1 + app/Http/Kernel.php | 11 -- .../Api/Daemon/DaemonAuthenticate.php | 18 +- .../Server/DatabaseBelongsToServer.php | 56 ------ .../Server/ScheduleBelongsToServer.php | 60 ------ .../Server/SubuserBelongsToServer.php | 67 ------- composer.json | 2 +- composer.lock | 15 +- database/factories/ModelFactory.php | 2 +- .../Admin/StatisticsControllerTest.php | 110 ----------- .../Base/AccountControllerTest.php | 0 .../Controllers/Base/IndexControllerTest.php | 181 ------------------ .../Base/SecurityControllerTest.php | 156 --------------- .../Http/Middleware/AdminAuthenticateTest.php | 9 +- .../Api/Application/AuthenticateUserTest.php | 11 +- .../{API => Api}/AuthenticateIPAccessTest.php | 7 +- .../{API => Api}/AuthenticateKeyTest.php | 11 +- .../Api/Daemon/DaemonAuthenticateTest.php | 101 ++++++++-- .../{API => Api}/SetSessionDriverTest.php | 2 +- .../Unit/Http/Middleware/AuthenticateTest.php | 5 +- .../Middleware/DaemonAuthenticateTest.php | 78 -------- .../Server/AccessingValidServerTest.php | 14 +- .../Server/AuthenticateAsSubuserTest.php | 7 +- .../Server/DatabaseBelongsToServerTest.php | 92 --------- .../Server/ScheduleBelongsToServerTest.php | 81 -------- .../Server/SubuserBelongsToServerTest.php | 156 --------------- 26 files changed, 140 insertions(+), 1113 deletions(-) create mode 100644 .phpunit.result.cache delete mode 100644 app/Http/Middleware/Server/DatabaseBelongsToServer.php delete mode 100644 app/Http/Middleware/Server/ScheduleBelongsToServer.php delete mode 100644 app/Http/Middleware/Server/SubuserBelongsToServer.php delete mode 100644 tests/Unit/Http/Controllers/Admin/StatisticsControllerTest.php delete mode 100644 tests/Unit/Http/Controllers/Base/AccountControllerTest.php delete mode 100644 tests/Unit/Http/Controllers/Base/IndexControllerTest.php delete mode 100644 tests/Unit/Http/Controllers/Base/SecurityControllerTest.php rename tests/Unit/Http/Middleware/{API => Api}/AuthenticateIPAccessTest.php (92%) rename tests/Unit/Http/Middleware/{API => Api}/AuthenticateKeyTest.php (96%) rename tests/Unit/Http/Middleware/{API => Api}/SetSessionDriverTest.php (96%) delete mode 100644 tests/Unit/Http/Middleware/DaemonAuthenticateTest.php delete mode 100644 tests/Unit/Http/Middleware/Server/DatabaseBelongsToServerTest.php delete mode 100644 tests/Unit/Http/Middleware/Server/ScheduleBelongsToServerTest.php delete mode 100644 tests/Unit/Http/Middleware/Server/SubuserBelongsToServerTest.php diff --git a/.phpunit.result.cache b/.phpunit.result.cache new file mode 100644 index 000000000..f220749fb --- /dev/null +++ b/.phpunit.result.cache @@ -0,0 +1 @@ +C:37:"PHPUnit\Runner\DefaultTestResultCache":21204:{a:2:{s:7:"defects";a:37:{s:79:"Tests\Unit\Http\Controllers\Admin\StatisticsControllerTest::testIndexController";i:4;s:73:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testIndexController";i:4;s:74:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusController";i:4;s:96:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerNotInstalled";i:4;s:95:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerIsSuspended";i:4;s:103:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithServerConnectionException";i:4;s:94:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithRequestException";i:4;s:87:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWithout2FactorEnabled";i:4;s:84:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWith2FactorEnabled";i:4;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #0";i:4;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #1";i:4;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #0";i:4;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #1";i:4;s:82:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNoUserDefined";i:6;s:81:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNonAdminUser";i:6;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithInvalidIP";i:6;s:73:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidIdentifier";i:6;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidTokenForIdentifier";i:6;s:105:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldContinueIfRouteIsExempted";i:4;s:103:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoTokenIsProvided";i:4;s:99:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoNodeIsFound";i:4;s:93:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testSuccessfulMiddlewareProcess";i:4;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserDoesNotExist";i:6;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserIsNotAnAdmin";i:6;s:62:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedOutUser";i:6;s:76:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testValidDaemonConnection";i:4;s:81:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testIgnoredRouteShouldContinue";i:4;s:85:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testExceptionThrownIfMissingHeader";i:3;s:100:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsSuspended";i:6;s:103:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsNotInstalled";i:6;s:98:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testExceptionIsThrownIfNoTokenIsFound";i:6;s:107:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfNoDatabaseRecordFound";i:6;s:119:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfDatabaseServerDoesNotMatchCurrent";i:6;s:115:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testExceptionIsThrownIfScheduleDoesNotBelongToServer";i:6;s:123:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfSubuserServerDoesNotMatchRequestServer";i:6;s:101:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfNoSubuserIsFound";i:6;s:100:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNodeIsNotFound";i:5;}s:5:"times";a:167:{s:81:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testSmtpDriverSelection";d:0.692;s:98:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testSmtpDriverSelectionWithOptionsPassed";d:0.038;s:84:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPHPMailDriverSelection";d:0.046;s:84:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMailgunDriverSelection";d:0.05;s:101:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMailgunDriverSelectionWithOptionsPassed";d:0.037;s:85:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMandrillDriverSelection";d:0.052;s:102:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMandrillDriverSelectionWithOptionsPassed";d:0.038;s:85:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPostmarkDriverSelection";d:0.051;s:102:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPostmarkDriverSelectionWithOptionsPassed";d:0.037;s:77:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testLocationIsDeleted";d:0.106;s:93:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testLocationIsDeletedIfPassedInOption";d:0.042;s:106:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testInteractiveEnvironmentAllowsReAttemptingSearch";d:0.06;s:115:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testNonInteractiveEnvironmentThrowsErrorIfNoLocationIsFound";d:0.041;s:94:"Tests\Unit\Commands\Location\MakeLocationCommandTest::testLocationIsCreatedWithNoOptionsPassed";d:0.044;s:95:"Tests\Unit\Commands\Location\MakeLocationCommandTest::testLocationIsCreatedWhenOptionsArePassed";d:0.045;s:109:"Tests\Unit\Commands\Maintenance\CleanServiceBackupFilesCommandTest::testCommandCleansFilesMoreThan5MinutesOld";d:0.044;s:114:"Tests\Unit\Commands\Maintenance\CleanServiceBackupFilesCommandTest::testCommandDoesNotCleanFileLessThan5MinutesOld";d:0.039;s:77:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleIsQueued";d:0.062;s:94:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleWithNoTasksIsNotProcessed";d:0.042;s:127:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleWithTasksObjectThatIsNotInstanceOfCollectionIsNotProcessed";d:0.042;s:69:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendAction";d:0.149;s:74:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendWithFilters";d:0.05;s:79:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendWithEmptyOptions";d:0.043;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #0";d:0.043;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #1";d:0.038;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #2";d:0.038;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #3";d:0.038;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #4";d:0.038;s:72:"Tests\Unit\Commands\User\DeleteUserCommandTest::testCommandWithNoOptions";d:0.065;s:83:"Tests\Unit\Commands\User\DeleteUserCommandTest::testCommandWithInvalidInitialSearch";d:0.047;s:67:"Tests\Unit\Commands\User\DeleteUserCommandTest::testReSearchAbility";d:0.044;s:102:"Tests\Unit\Commands\User\DeleteUserCommandTest::testAnsweringNoToDeletionConfirmationWillNotDeleteUser";d:0.043;s:81:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithSingleResult";d:0.043;s:84:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithMultipleResults";d:0.045;s:78:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithNoResults";d:0.037;s:97:"Tests\Unit\Commands\User\DisableTwoFactorCommandTest::testTwoFactorIsDisabledWhenNoOptionIsPassed";d:0.041;s:95:"Tests\Unit\Commands\User\DisableTwoFactorCommandTest::testTwoFactorIsDisabledWhenOptionIsPassed";d:0.041;s:76:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithNoPassedOptions";d:0.053;s:77:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithNoPasswordOption";d:0.044;s:74:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithOptionsPassed";d:0.043;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #0";d:0.603;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #1";d:0.038;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #2";d:0.038;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #3";d:0.038;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #4";d:0.041;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #5";d:0.037;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #6";d:0.038;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #7";d:0.037;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #8";d:0.037;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #9";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #10";d:0.038;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #11";d:0.038;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #12";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #13";d:0.039;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #14";d:0.038;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #15";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #16";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #17";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #18";d:0.043;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #19";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #20";d:0.037;s:77:"Tests\Unit\Http\Controllers\Admin\DatabaseControllerTest::testIndexController";d:0.174;s:76:"Tests\Unit\Http\Controllers\Admin\DatabaseControllerTest::testViewController";d:0.047;s:57:"Tests\Unit\Http\Controllers\MailControllerTest::testIndex";d:0.052;s:79:"Tests\Unit\Http\Controllers\Admin\StatisticsControllerTest::testIndexController";d:0.145;s:73:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testIndexController";d:0.054;s:74:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusController";d:0.039;s:96:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerNotInstalled";d:0.04;s:95:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerIsSuspended";d:0.037;s:103:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithServerConnectionException";d:0.038;s:94:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithRequestException";d:0.04;s:87:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWithout2FactorEnabled";d:0.068;s:84:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWith2FactorEnabled";d:0.042;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #0";d:0.044;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #1";d:0.041;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #0";d:0.049;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #1";d:0.043;s:82:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNoUserDefined";d:0.044;s:81:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNonAdminUser";d:0.047;s:78:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testAdminUser";d:0.041;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithNoIPRestrictions";d:0.043;s:72:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithValidIP";d:0.044;s:84:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testValidIPAgainstCIDRRange";d:0.043;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithInvalidIP";d:0.04;s:89:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testMissingBearerTokenThrowsException";d:0.049;s:73:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidIdentifier";d:0.042;s:66:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testValidToken";d:0.045;s:77:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testValidTokenWithUserKey";d:0.044;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testAccessWithoutToken";d:0.042;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidTokenForIdentifier";d:0.041;s:105:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldContinueIfRouteIsExempted";d:0.05;s:103:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoTokenIsProvided";d:0.038;s:99:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoNodeIsFound";d:0.035;s:93:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testSuccessfulMiddlewareProcess";d:0.04;s:67:"Tests\Unit\Http\Middleware\API\SetSessionDriverTest::testMiddleware";d:0.039;s:76:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testAdminsAreAuthenticated";d:0.049;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserDoesNotExist";d:0.043;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserIsNotAnAdmin";d:0.041;s:61:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedInUser";d:0.04;s:62:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedOutUser";d:0.039;s:76:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testValidDaemonConnection";d:0.04;s:81:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testIgnoredRouteShouldContinue";d:0.037;s:85:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testExceptionThrownIfMissingHeader";d:0.038;s:76:"Tests\Unit\Http\Middleware\LanguageMiddlewareTest::testLanguageIsSetForGuest";d:0.044;s:89:"Tests\Unit\Http\Middleware\LanguageMiddlewareTest::testLanguageIsSetWithAuthenticatedUser";d:0.042;s:64:"Tests\Unit\Http\Middleware\MaintenanceMiddlewareTest::testHandle";d:0.047;s:81:"Tests\Unit\Http\Middleware\MaintenanceMiddlewareTest::testHandleInMaintenanceMode";d:0.047;s:89:"Tests\Unit\Http\Middleware\RedirectIfAuthenticatedTest::testAuthenticatedUserIsRedirected";d:0.047;s:95:"Tests\Unit\Http\Middleware\RedirectIfAuthenticatedTest::testNonAuthenticatedUserIsNotRedirected";d:0.037;s:85:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestMissingUser";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #0";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #1";d:0.045;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #2";d:0.043;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #3";d:0.041;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #4";d:0.04;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #5";d:0.041;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #6";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #7";d:0.041;s:95:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorRequirementDisabled";d:0.042;s:103:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorRequirementWithInvalidValue";d:0.041;s:118:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsAdminUserWith2FADisabled";d:0.043;s:117:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsAdminUserWith2FAEnabled";d:0.04;s:102:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsNonAdmin";d:0.05;s:115:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAllUsersAsUserWith2FADisabled";d:0.042;s:114:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAllUsersAsUserWith2FAEnabled";d:0.041;s:100:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsSuspended";d:0.052;s:103:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsNotInstalled";d:0.042;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #0";d:0.038;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #1";d:0.039;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #2";d:0.039;s:82:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testValidServerProcess";d:0.042;s:85:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testSuccessfulMiddleware";d:0.05;s:98:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testExceptionIsThrownIfNoTokenIsFound";d:0.043;s:87:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testSuccessfulMiddleware";d:0.045;s:107:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfNoDatabaseRecordFound";d:0.043;s:119:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfDatabaseServerDoesNotMatchCurrent";d:0.042;s:87:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testSuccessfulMiddleware";d:0.051;s:115:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testExceptionIsThrownIfScheduleDoesNotBelongToServer";d:0.042;s:86:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testSuccessfulMiddleware";d:0.044;s:102:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testSuccessfulMiddlewareWhenPatchRequest";d:0.041;s:104:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfUserTriesToEditSelf";d:0.044;s:123:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfSubuserServerDoesNotMatchRequestServer";d:0.041;s:101:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfNoSubuserIsFound";d:0.041;s:82:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testNoUserDefined";d:0.038;s:81:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testNonAdminUser";d:0.041;s:78:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testAdminUser";d:0.042;s:81:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithNoIPRestrictions";d:0.044;s:72:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithValidIP";d:0.045;s:84:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testValidIPAgainstCIDRRange";d:0.046;s:74:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithInvalidIP";d:0.04;s:89:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testMissingBearerTokenThrowsException";d:0.051;s:73:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testInvalidIdentifier";d:0.042;s:66:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testValidToken";d:0.046;s:77:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testValidTokenWithUserKey";d:0.042;s:74:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testAccessWithoutToken";d:0.042;s:81:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testInvalidTokenForIdentifier";d:0.041;s:67:"Tests\Unit\Http\Middleware\Api\SetSessionDriverTest::testMiddleware";d:0.039;s:108:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect";d:0.034;s:101:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenIsNotValid";d:0.113;s:100:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNodeIsNotFound";d:0.038;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #0";d:0.04;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #1";d:0.038;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #2";d:0.038;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #3";d:0.037;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #4";d:0.039;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #5";d:0.042;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #6";d:0.038;}}} \ No newline at end of file diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index c6d537a26..ade6ff4a7 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -30,10 +30,7 @@ use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Pterodactyl\Http\Middleware\Server\AccessingValidServer; use Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser; use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate; -use Pterodactyl\Http\Middleware\Server\SubuserBelongsToServer; use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; -use Pterodactyl\Http\Middleware\Server\DatabaseBelongsToServer; -use Pterodactyl\Http\Middleware\Server\ScheduleBelongsToServer; use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientApiBindings; @@ -113,14 +110,6 @@ class Kernel extends HttpKernel 'recaptcha' => VerifyReCaptcha::class, 'node.maintenance' => MaintenanceMiddleware::class, - // Server specific middleware (used for authenticating access to resources) - // - // These are only used for individual server authentication, and not global - // actions from other resources. They are defined in the route files. - 'server..database' => DatabaseBelongsToServer::class, - 'server..subuser' => SubuserBelongsToServer::class, - 'server..schedule' => ScheduleBelongsToServer::class, - // API Specific Middleware 'api..key' => AuthenticateKey::class, ]; diff --git a/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php b/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php index a2b1e716e..bc365e63c 100644 --- a/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php +++ b/app/Http/Middleware/Api/Daemon/DaemonAuthenticate.php @@ -5,8 +5,8 @@ namespace Pterodactyl\Http\Middleware\Api\Daemon; use Closure; use Illuminate\Http\Request; use Illuminate\Contracts\Encryption\Encrypter; +use Pterodactyl\Repositories\Eloquent\NodeRepository; use Symfony\Component\HttpKernel\Exception\HttpException; -use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -14,10 +14,15 @@ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; class DaemonAuthenticate { /** - * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + * @var \Pterodactyl\Repositories\Eloquent\NodeRepository */ private $repository; + /** + * @var \Illuminate\Contracts\Encryption\Encrypter + */ + private $encrypter; + /** * Daemon routes that this middleware should be skipped on. * @@ -27,18 +32,13 @@ class DaemonAuthenticate 'daemon.configuration', ]; - /** - * @var \Illuminate\Contracts\Encryption\Encrypter - */ - private $encrypter; - /** * DaemonAuthenticate constructor. * * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter - * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository + * @param \Pterodactyl\Repositories\Eloquent\NodeRepository $repository */ - public function __construct(Encrypter $encrypter, NodeRepositoryInterface $repository) + public function __construct(Encrypter $encrypter, NodeRepository $repository) { $this->repository = $repository; $this->encrypter = $encrypter; diff --git a/app/Http/Middleware/Server/DatabaseBelongsToServer.php b/app/Http/Middleware/Server/DatabaseBelongsToServer.php deleted file mode 100644 index 169b67525..000000000 --- a/app/Http/Middleware/Server/DatabaseBelongsToServer.php +++ /dev/null @@ -1,56 +0,0 @@ -repository = $repository; - } - - /** - * Check if a database being requested belongs to the currently loaded server. - * If it does not, throw a 404 error, otherwise continue on with the request - * and set an attribute with the database. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle(Request $request, Closure $next) - { - $server = $request->attributes->get('server'); - $database = $request->input('database') ?? $request->route()->parameter('database'); - - if (! is_digit($database)) { - throw new NotFoundHttpException; - } - - $database = $this->repository->find($database); - if (is_null($database) || $database->server_id !== $server->id) { - throw new NotFoundHttpException; - } - - $request->attributes->set('database', $database); - - return $next($request); - } -} diff --git a/app/Http/Middleware/Server/ScheduleBelongsToServer.php b/app/Http/Middleware/Server/ScheduleBelongsToServer.php deleted file mode 100644 index b76636291..000000000 --- a/app/Http/Middleware/Server/ScheduleBelongsToServer.php +++ /dev/null @@ -1,60 +0,0 @@ -hashids = $hashids; - $this->repository = $repository; - } - - /** - * Determine if a task is assigned to the active server. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - * - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException - */ - public function handle(Request $request, Closure $next) - { - $server = $request->attributes->get('server'); - - $scheduleId = $this->hashids->decodeFirst($request->route()->parameter('schedule'), 0); - $schedule = $this->repository->getScheduleWithTasks($scheduleId); - - if ($schedule->server_id !== $server->id) { - throw new NotFoundHttpException; - } - - $request->attributes->set('schedule', $schedule); - - return $next($request); - } -} diff --git a/app/Http/Middleware/Server/SubuserBelongsToServer.php b/app/Http/Middleware/Server/SubuserBelongsToServer.php deleted file mode 100644 index 7efc64990..000000000 --- a/app/Http/Middleware/Server/SubuserBelongsToServer.php +++ /dev/null @@ -1,67 +0,0 @@ -hashids = $hashids; - $this->repository = $repository; - } - - /** - * Determine if a user has permission to access and modify subuser. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException - */ - public function handle(Request $request, Closure $next) - { - $server = $request->attributes->get('server'); - - $hash = $request->route()->parameter('subuser', 0); - $subuser = $this->repository->find($this->hashids->decodeFirst($hash, 0)); - if (is_null($subuser) || $subuser->server_id !== $server->id) { - throw new NotFoundHttpException; - } - - if ($request->method() === 'PATCH') { - if ($subuser->user_id === $request->user()->id) { - throw new DisplayException(trans('exceptions.subusers.editing_self')); - } - } - - $request->attributes->set('subuser', $subuser); - - return $next($request); - } -} diff --git a/composer.json b/composer.json index 5c85ecdf4..0ccfab915 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,7 @@ "require-dev": { "barryvdh/laravel-debugbar": "^3.3", "barryvdh/laravel-ide-helper": "^2.7", - "codedungeon/phpunit-result-printer": "0.25.1", + "codedungeon/phpunit-result-printer": "^0.28.0", "friendsofphp/php-cs-fixer": "2.16.1", "fzaninotto/faker": "^1.9", "laravel/dusk": "^6.3", diff --git a/composer.lock b/composer.lock index 0ddb8d227..7593ee4b4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "79af09c27859a87b05f58bde40a29576", + "content-hash": "155b8e930e604c0476fa975b1084ca3f", "packages": [ { "name": "appstract/laravel-blade-directives", @@ -6252,16 +6252,16 @@ }, { "name": "codedungeon/phpunit-result-printer", - "version": "0.25.1", + "version": "0.28.0", "source": { "type": "git", "url": "https://github.com/mikeerickson/phpunit-pretty-result-printer.git", - "reference": "4a689ac40366eb4adf166cf4676da7ef30d82315" + "reference": "bc023b0311589bee19047425083163ffa3f0cf88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mikeerickson/phpunit-pretty-result-printer/zipball/4a689ac40366eb4adf166cf4676da7ef30d82315", - "reference": "4a689ac40366eb4adf166cf4676da7ef30d82315", + "url": "https://api.github.com/repos/mikeerickson/phpunit-pretty-result-printer/zipball/bc023b0311589bee19047425083163ffa3f0cf88", + "reference": "bc023b0311589bee19047425083163ffa3f0cf88", "shasum": "" }, "require": { @@ -6269,10 +6269,9 @@ "codedungeon/php-cli-colors": "^1.10.2", "hassankhan/config": "^0.11.2", "php": "^7.1", - "symfony/yaml": "^2.7|^3.0|^4.0" + "symfony/yaml": "^2.7|^3.0|^4.0|^5.0" }, "require-dev": { - "phpunit/phpunit": "7.5.*", "spatie/phpunit-watcher": "^1.6" }, "type": "library", @@ -6301,7 +6300,7 @@ "result-printer", "testing" ], - "time": "2019-02-01T19:13:43+00:00" + "time": "2020-06-24T00:16:05+00:00" }, { "name": "composer/ca-bundle", diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index b55a92804..db12c08d9 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -95,7 +95,7 @@ $factory->define(Pterodactyl\Models\Node::class, function (Faker $faker) { 'disk_overallocate' => 0, 'upload_size' => 100, 'daemon_token_id' => Str::random(Node::DAEMON_TOKEN_ID_LENGTH), - 'daemon_token' => Str::random(Node::DAEMON_TOKEN_LENGTH), + 'daemon_token' => encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH)), 'daemonListen' => 8080, 'daemonSFTP' => 2022, 'daemonBase' => '/var/lib/pterodactyl/volumes', 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 index b24a2c227..b9199a67a 100644 --- a/tests/Unit/Http/Middleware/Server/AuthenticateAsSubuserTest.php +++ b/tests/Unit/Http/Middleware/Server/AuthenticateAsSubuserTest.php @@ -8,6 +8,7 @@ use Tests\Unit\Http\Middleware\MiddlewareTestCase; use Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser; use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; class AuthenticateAsSubuserTest extends MiddlewareTestCase { @@ -44,12 +45,12 @@ class AuthenticateAsSubuserTest extends MiddlewareTestCase /** * 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() { + $this->expectException(AccessDeniedHttpException::class); + $this->expectExceptionMessage('This account does not have permission to access this server.'); + $model = factory(Server::class)->make(); $user = $this->setRequestUser(); $this->setRequestAttribute('server', $model); 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); - } -} From 34916e7cafbb154da3cab3141afbd2e73aa32fc4 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 23 Jun 2020 22:10:54 -0700 Subject: [PATCH 27/63] Fix job task runner test --- .phpunit.result.cache | 2 +- tests/Unit/Jobs/Schedule/RunTaskJobTest.php | 104 +++++++++----------- 2 files changed, 50 insertions(+), 56 deletions(-) diff --git a/.phpunit.result.cache b/.phpunit.result.cache index f220749fb..90c076199 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -C:37:"PHPUnit\Runner\DefaultTestResultCache":21204:{a:2:{s:7:"defects";a:37:{s:79:"Tests\Unit\Http\Controllers\Admin\StatisticsControllerTest::testIndexController";i:4;s:73:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testIndexController";i:4;s:74:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusController";i:4;s:96:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerNotInstalled";i:4;s:95:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerIsSuspended";i:4;s:103:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithServerConnectionException";i:4;s:94:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithRequestException";i:4;s:87:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWithout2FactorEnabled";i:4;s:84:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWith2FactorEnabled";i:4;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #0";i:4;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #1";i:4;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #0";i:4;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #1";i:4;s:82:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNoUserDefined";i:6;s:81:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNonAdminUser";i:6;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithInvalidIP";i:6;s:73:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidIdentifier";i:6;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidTokenForIdentifier";i:6;s:105:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldContinueIfRouteIsExempted";i:4;s:103:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoTokenIsProvided";i:4;s:99:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoNodeIsFound";i:4;s:93:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testSuccessfulMiddlewareProcess";i:4;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserDoesNotExist";i:6;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserIsNotAnAdmin";i:6;s:62:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedOutUser";i:6;s:76:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testValidDaemonConnection";i:4;s:81:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testIgnoredRouteShouldContinue";i:4;s:85:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testExceptionThrownIfMissingHeader";i:3;s:100:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsSuspended";i:6;s:103:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsNotInstalled";i:6;s:98:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testExceptionIsThrownIfNoTokenIsFound";i:6;s:107:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfNoDatabaseRecordFound";i:6;s:119:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfDatabaseServerDoesNotMatchCurrent";i:6;s:115:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testExceptionIsThrownIfScheduleDoesNotBelongToServer";i:6;s:123:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfSubuserServerDoesNotMatchRequestServer";i:6;s:101:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfNoSubuserIsFound";i:6;s:100:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNodeIsNotFound";i:5;}s:5:"times";a:167:{s:81:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testSmtpDriverSelection";d:0.692;s:98:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testSmtpDriverSelectionWithOptionsPassed";d:0.038;s:84:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPHPMailDriverSelection";d:0.046;s:84:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMailgunDriverSelection";d:0.05;s:101:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMailgunDriverSelectionWithOptionsPassed";d:0.037;s:85:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMandrillDriverSelection";d:0.052;s:102:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMandrillDriverSelectionWithOptionsPassed";d:0.038;s:85:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPostmarkDriverSelection";d:0.051;s:102:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPostmarkDriverSelectionWithOptionsPassed";d:0.037;s:77:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testLocationIsDeleted";d:0.106;s:93:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testLocationIsDeletedIfPassedInOption";d:0.042;s:106:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testInteractiveEnvironmentAllowsReAttemptingSearch";d:0.06;s:115:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testNonInteractiveEnvironmentThrowsErrorIfNoLocationIsFound";d:0.041;s:94:"Tests\Unit\Commands\Location\MakeLocationCommandTest::testLocationIsCreatedWithNoOptionsPassed";d:0.044;s:95:"Tests\Unit\Commands\Location\MakeLocationCommandTest::testLocationIsCreatedWhenOptionsArePassed";d:0.045;s:109:"Tests\Unit\Commands\Maintenance\CleanServiceBackupFilesCommandTest::testCommandCleansFilesMoreThan5MinutesOld";d:0.044;s:114:"Tests\Unit\Commands\Maintenance\CleanServiceBackupFilesCommandTest::testCommandDoesNotCleanFileLessThan5MinutesOld";d:0.039;s:77:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleIsQueued";d:0.062;s:94:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleWithNoTasksIsNotProcessed";d:0.042;s:127:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleWithTasksObjectThatIsNotInstanceOfCollectionIsNotProcessed";d:0.042;s:69:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendAction";d:0.149;s:74:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendWithFilters";d:0.05;s:79:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendWithEmptyOptions";d:0.043;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #0";d:0.043;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #1";d:0.038;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #2";d:0.038;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #3";d:0.038;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #4";d:0.038;s:72:"Tests\Unit\Commands\User\DeleteUserCommandTest::testCommandWithNoOptions";d:0.065;s:83:"Tests\Unit\Commands\User\DeleteUserCommandTest::testCommandWithInvalidInitialSearch";d:0.047;s:67:"Tests\Unit\Commands\User\DeleteUserCommandTest::testReSearchAbility";d:0.044;s:102:"Tests\Unit\Commands\User\DeleteUserCommandTest::testAnsweringNoToDeletionConfirmationWillNotDeleteUser";d:0.043;s:81:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithSingleResult";d:0.043;s:84:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithMultipleResults";d:0.045;s:78:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithNoResults";d:0.037;s:97:"Tests\Unit\Commands\User\DisableTwoFactorCommandTest::testTwoFactorIsDisabledWhenNoOptionIsPassed";d:0.041;s:95:"Tests\Unit\Commands\User\DisableTwoFactorCommandTest::testTwoFactorIsDisabledWhenOptionIsPassed";d:0.041;s:76:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithNoPassedOptions";d:0.053;s:77:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithNoPasswordOption";d:0.044;s:74:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithOptionsPassed";d:0.043;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #0";d:0.603;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #1";d:0.038;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #2";d:0.038;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #3";d:0.038;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #4";d:0.041;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #5";d:0.037;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #6";d:0.038;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #7";d:0.037;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #8";d:0.037;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #9";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #10";d:0.038;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #11";d:0.038;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #12";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #13";d:0.039;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #14";d:0.038;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #15";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #16";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #17";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #18";d:0.043;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #19";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #20";d:0.037;s:77:"Tests\Unit\Http\Controllers\Admin\DatabaseControllerTest::testIndexController";d:0.174;s:76:"Tests\Unit\Http\Controllers\Admin\DatabaseControllerTest::testViewController";d:0.047;s:57:"Tests\Unit\Http\Controllers\MailControllerTest::testIndex";d:0.052;s:79:"Tests\Unit\Http\Controllers\Admin\StatisticsControllerTest::testIndexController";d:0.145;s:73:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testIndexController";d:0.054;s:74:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusController";d:0.039;s:96:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerNotInstalled";d:0.04;s:95:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerIsSuspended";d:0.037;s:103:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithServerConnectionException";d:0.038;s:94:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithRequestException";d:0.04;s:87:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWithout2FactorEnabled";d:0.068;s:84:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWith2FactorEnabled";d:0.042;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #0";d:0.044;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #1";d:0.041;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #0";d:0.049;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #1";d:0.043;s:82:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNoUserDefined";d:0.044;s:81:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNonAdminUser";d:0.047;s:78:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testAdminUser";d:0.041;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithNoIPRestrictions";d:0.043;s:72:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithValidIP";d:0.044;s:84:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testValidIPAgainstCIDRRange";d:0.043;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithInvalidIP";d:0.04;s:89:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testMissingBearerTokenThrowsException";d:0.049;s:73:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidIdentifier";d:0.042;s:66:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testValidToken";d:0.045;s:77:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testValidTokenWithUserKey";d:0.044;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testAccessWithoutToken";d:0.042;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidTokenForIdentifier";d:0.041;s:105:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldContinueIfRouteIsExempted";d:0.05;s:103:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoTokenIsProvided";d:0.038;s:99:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoNodeIsFound";d:0.035;s:93:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testSuccessfulMiddlewareProcess";d:0.04;s:67:"Tests\Unit\Http\Middleware\API\SetSessionDriverTest::testMiddleware";d:0.039;s:76:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testAdminsAreAuthenticated";d:0.049;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserDoesNotExist";d:0.043;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserIsNotAnAdmin";d:0.041;s:61:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedInUser";d:0.04;s:62:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedOutUser";d:0.039;s:76:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testValidDaemonConnection";d:0.04;s:81:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testIgnoredRouteShouldContinue";d:0.037;s:85:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testExceptionThrownIfMissingHeader";d:0.038;s:76:"Tests\Unit\Http\Middleware\LanguageMiddlewareTest::testLanguageIsSetForGuest";d:0.044;s:89:"Tests\Unit\Http\Middleware\LanguageMiddlewareTest::testLanguageIsSetWithAuthenticatedUser";d:0.042;s:64:"Tests\Unit\Http\Middleware\MaintenanceMiddlewareTest::testHandle";d:0.047;s:81:"Tests\Unit\Http\Middleware\MaintenanceMiddlewareTest::testHandleInMaintenanceMode";d:0.047;s:89:"Tests\Unit\Http\Middleware\RedirectIfAuthenticatedTest::testAuthenticatedUserIsRedirected";d:0.047;s:95:"Tests\Unit\Http\Middleware\RedirectIfAuthenticatedTest::testNonAuthenticatedUserIsNotRedirected";d:0.037;s:85:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestMissingUser";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #0";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #1";d:0.045;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #2";d:0.043;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #3";d:0.041;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #4";d:0.04;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #5";d:0.041;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #6";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #7";d:0.041;s:95:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorRequirementDisabled";d:0.042;s:103:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorRequirementWithInvalidValue";d:0.041;s:118:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsAdminUserWith2FADisabled";d:0.043;s:117:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsAdminUserWith2FAEnabled";d:0.04;s:102:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsNonAdmin";d:0.05;s:115:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAllUsersAsUserWith2FADisabled";d:0.042;s:114:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAllUsersAsUserWith2FAEnabled";d:0.041;s:100:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsSuspended";d:0.052;s:103:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsNotInstalled";d:0.042;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #0";d:0.038;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #1";d:0.039;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #2";d:0.039;s:82:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testValidServerProcess";d:0.042;s:85:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testSuccessfulMiddleware";d:0.05;s:98:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testExceptionIsThrownIfNoTokenIsFound";d:0.043;s:87:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testSuccessfulMiddleware";d:0.045;s:107:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfNoDatabaseRecordFound";d:0.043;s:119:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfDatabaseServerDoesNotMatchCurrent";d:0.042;s:87:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testSuccessfulMiddleware";d:0.051;s:115:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testExceptionIsThrownIfScheduleDoesNotBelongToServer";d:0.042;s:86:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testSuccessfulMiddleware";d:0.044;s:102:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testSuccessfulMiddlewareWhenPatchRequest";d:0.041;s:104:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfUserTriesToEditSelf";d:0.044;s:123:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfSubuserServerDoesNotMatchRequestServer";d:0.041;s:101:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfNoSubuserIsFound";d:0.041;s:82:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testNoUserDefined";d:0.038;s:81:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testNonAdminUser";d:0.041;s:78:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testAdminUser";d:0.042;s:81:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithNoIPRestrictions";d:0.044;s:72:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithValidIP";d:0.045;s:84:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testValidIPAgainstCIDRRange";d:0.046;s:74:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithInvalidIP";d:0.04;s:89:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testMissingBearerTokenThrowsException";d:0.051;s:73:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testInvalidIdentifier";d:0.042;s:66:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testValidToken";d:0.046;s:77:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testValidTokenWithUserKey";d:0.042;s:74:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testAccessWithoutToken";d:0.042;s:81:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testInvalidTokenForIdentifier";d:0.041;s:67:"Tests\Unit\Http\Middleware\Api\SetSessionDriverTest::testMiddleware";d:0.039;s:108:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect";d:0.034;s:101:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenIsNotValid";d:0.113;s:100:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNodeIsNotFound";d:0.038;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #0";d:0.04;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #1";d:0.038;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #2";d:0.038;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #3";d:0.037;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #4";d:0.039;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #5";d:0.042;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #6";d:0.038;}}} \ No newline at end of file +C:37:"PHPUnit\Runner\DefaultTestResultCache":22007:{a:2:{s:7:"defects";a:42:{s:79:"Tests\Unit\Http\Controllers\Admin\StatisticsControllerTest::testIndexController";i:4;s:73:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testIndexController";i:4;s:74:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusController";i:4;s:96:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerNotInstalled";i:4;s:95:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerIsSuspended";i:4;s:103:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithServerConnectionException";i:4;s:94:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithRequestException";i:4;s:87:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWithout2FactorEnabled";i:4;s:84:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWith2FactorEnabled";i:4;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #0";i:4;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #1";i:4;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #0";i:4;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #1";i:4;s:82:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNoUserDefined";i:6;s:81:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNonAdminUser";i:6;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithInvalidIP";i:6;s:73:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidIdentifier";i:6;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidTokenForIdentifier";i:6;s:105:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldContinueIfRouteIsExempted";i:4;s:103:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoTokenIsProvided";i:4;s:99:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoNodeIsFound";i:4;s:93:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testSuccessfulMiddlewareProcess";i:4;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserDoesNotExist";i:6;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserIsNotAnAdmin";i:6;s:62:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedOutUser";i:6;s:76:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testValidDaemonConnection";i:4;s:81:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testIgnoredRouteShouldContinue";i:4;s:85:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testExceptionThrownIfMissingHeader";i:3;s:100:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsSuspended";i:6;s:103:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsNotInstalled";i:6;s:98:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testExceptionIsThrownIfNoTokenIsFound";i:6;s:107:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfNoDatabaseRecordFound";i:6;s:119:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfDatabaseServerDoesNotMatchCurrent";i:6;s:115:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testExceptionIsThrownIfScheduleDoesNotBelongToServer";i:6;s:123:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfSubuserServerDoesNotMatchRequestServer";i:6;s:101:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfNoSubuserIsFound";i:6;s:100:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNodeIsNotFound";i:5;s:56:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testPowerAction";i:4;s:58:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testCommandAction";i:4;s:67:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testNextTaskQueuedIfExists";i:4;s:69:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testInvalidActionPassedToJob";i:6;s:83:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testScheduleMarkedAsDisabledDoesNotProcess";i:4;}s:5:"times";a:172:{s:81:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testSmtpDriverSelection";d:0.692;s:98:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testSmtpDriverSelectionWithOptionsPassed";d:0.038;s:84:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPHPMailDriverSelection";d:0.046;s:84:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMailgunDriverSelection";d:0.05;s:101:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMailgunDriverSelectionWithOptionsPassed";d:0.037;s:85:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMandrillDriverSelection";d:0.052;s:102:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMandrillDriverSelectionWithOptionsPassed";d:0.038;s:85:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPostmarkDriverSelection";d:0.051;s:102:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPostmarkDriverSelectionWithOptionsPassed";d:0.037;s:77:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testLocationIsDeleted";d:0.106;s:93:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testLocationIsDeletedIfPassedInOption";d:0.042;s:106:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testInteractiveEnvironmentAllowsReAttemptingSearch";d:0.06;s:115:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testNonInteractiveEnvironmentThrowsErrorIfNoLocationIsFound";d:0.041;s:94:"Tests\Unit\Commands\Location\MakeLocationCommandTest::testLocationIsCreatedWithNoOptionsPassed";d:0.044;s:95:"Tests\Unit\Commands\Location\MakeLocationCommandTest::testLocationIsCreatedWhenOptionsArePassed";d:0.045;s:109:"Tests\Unit\Commands\Maintenance\CleanServiceBackupFilesCommandTest::testCommandCleansFilesMoreThan5MinutesOld";d:0.044;s:114:"Tests\Unit\Commands\Maintenance\CleanServiceBackupFilesCommandTest::testCommandDoesNotCleanFileLessThan5MinutesOld";d:0.039;s:77:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleIsQueued";d:0.062;s:94:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleWithNoTasksIsNotProcessed";d:0.042;s:127:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleWithTasksObjectThatIsNotInstanceOfCollectionIsNotProcessed";d:0.042;s:69:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendAction";d:0.149;s:74:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendWithFilters";d:0.05;s:79:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendWithEmptyOptions";d:0.043;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #0";d:0.043;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #1";d:0.038;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #2";d:0.038;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #3";d:0.038;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #4";d:0.038;s:72:"Tests\Unit\Commands\User\DeleteUserCommandTest::testCommandWithNoOptions";d:0.065;s:83:"Tests\Unit\Commands\User\DeleteUserCommandTest::testCommandWithInvalidInitialSearch";d:0.047;s:67:"Tests\Unit\Commands\User\DeleteUserCommandTest::testReSearchAbility";d:0.044;s:102:"Tests\Unit\Commands\User\DeleteUserCommandTest::testAnsweringNoToDeletionConfirmationWillNotDeleteUser";d:0.043;s:81:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithSingleResult";d:0.043;s:84:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithMultipleResults";d:0.045;s:78:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithNoResults";d:0.037;s:97:"Tests\Unit\Commands\User\DisableTwoFactorCommandTest::testTwoFactorIsDisabledWhenNoOptionIsPassed";d:0.041;s:95:"Tests\Unit\Commands\User\DisableTwoFactorCommandTest::testTwoFactorIsDisabledWhenOptionIsPassed";d:0.041;s:76:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithNoPassedOptions";d:0.053;s:77:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithNoPasswordOption";d:0.044;s:74:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithOptionsPassed";d:0.043;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #0";d:0.603;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #1";d:0.038;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #2";d:0.038;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #3";d:0.038;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #4";d:0.041;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #5";d:0.037;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #6";d:0.038;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #7";d:0.037;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #8";d:0.037;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #9";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #10";d:0.038;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #11";d:0.038;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #12";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #13";d:0.039;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #14";d:0.038;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #15";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #16";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #17";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #18";d:0.043;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #19";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #20";d:0.037;s:77:"Tests\Unit\Http\Controllers\Admin\DatabaseControllerTest::testIndexController";d:0.174;s:76:"Tests\Unit\Http\Controllers\Admin\DatabaseControllerTest::testViewController";d:0.047;s:57:"Tests\Unit\Http\Controllers\MailControllerTest::testIndex";d:0.052;s:79:"Tests\Unit\Http\Controllers\Admin\StatisticsControllerTest::testIndexController";d:0.145;s:73:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testIndexController";d:0.054;s:74:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusController";d:0.039;s:96:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerNotInstalled";d:0.04;s:95:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerIsSuspended";d:0.037;s:103:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithServerConnectionException";d:0.038;s:94:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithRequestException";d:0.04;s:87:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWithout2FactorEnabled";d:0.068;s:84:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWith2FactorEnabled";d:0.042;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #0";d:0.044;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #1";d:0.041;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #0";d:0.049;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #1";d:0.043;s:82:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNoUserDefined";d:0.044;s:81:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNonAdminUser";d:0.047;s:78:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testAdminUser";d:0.041;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithNoIPRestrictions";d:0.043;s:72:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithValidIP";d:0.044;s:84:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testValidIPAgainstCIDRRange";d:0.043;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithInvalidIP";d:0.04;s:89:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testMissingBearerTokenThrowsException";d:0.049;s:73:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidIdentifier";d:0.042;s:66:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testValidToken";d:0.045;s:77:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testValidTokenWithUserKey";d:0.044;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testAccessWithoutToken";d:0.042;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidTokenForIdentifier";d:0.041;s:105:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldContinueIfRouteIsExempted";d:0.05;s:103:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoTokenIsProvided";d:0.038;s:99:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoNodeIsFound";d:0.035;s:93:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testSuccessfulMiddlewareProcess";d:0.04;s:67:"Tests\Unit\Http\Middleware\API\SetSessionDriverTest::testMiddleware";d:0.039;s:76:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testAdminsAreAuthenticated";d:0.049;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserDoesNotExist";d:0.043;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserIsNotAnAdmin";d:0.041;s:61:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedInUser";d:0.04;s:62:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedOutUser";d:0.039;s:76:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testValidDaemonConnection";d:0.04;s:81:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testIgnoredRouteShouldContinue";d:0.037;s:85:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testExceptionThrownIfMissingHeader";d:0.038;s:76:"Tests\Unit\Http\Middleware\LanguageMiddlewareTest::testLanguageIsSetForGuest";d:0.044;s:89:"Tests\Unit\Http\Middleware\LanguageMiddlewareTest::testLanguageIsSetWithAuthenticatedUser";d:0.042;s:64:"Tests\Unit\Http\Middleware\MaintenanceMiddlewareTest::testHandle";d:0.047;s:81:"Tests\Unit\Http\Middleware\MaintenanceMiddlewareTest::testHandleInMaintenanceMode";d:0.047;s:89:"Tests\Unit\Http\Middleware\RedirectIfAuthenticatedTest::testAuthenticatedUserIsRedirected";d:0.047;s:95:"Tests\Unit\Http\Middleware\RedirectIfAuthenticatedTest::testNonAuthenticatedUserIsNotRedirected";d:0.037;s:85:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestMissingUser";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #0";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #1";d:0.045;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #2";d:0.043;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #3";d:0.041;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #4";d:0.04;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #5";d:0.041;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #6";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #7";d:0.041;s:95:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorRequirementDisabled";d:0.042;s:103:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorRequirementWithInvalidValue";d:0.041;s:118:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsAdminUserWith2FADisabled";d:0.043;s:117:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsAdminUserWith2FAEnabled";d:0.04;s:102:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsNonAdmin";d:0.05;s:115:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAllUsersAsUserWith2FADisabled";d:0.042;s:114:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAllUsersAsUserWith2FAEnabled";d:0.041;s:100:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsSuspended";d:0.052;s:103:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsNotInstalled";d:0.042;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #0";d:0.038;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #1";d:0.039;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #2";d:0.039;s:82:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testValidServerProcess";d:0.042;s:85:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testSuccessfulMiddleware";d:0.05;s:98:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testExceptionIsThrownIfNoTokenIsFound";d:0.043;s:87:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testSuccessfulMiddleware";d:0.045;s:107:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfNoDatabaseRecordFound";d:0.043;s:119:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfDatabaseServerDoesNotMatchCurrent";d:0.042;s:87:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testSuccessfulMiddleware";d:0.051;s:115:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testExceptionIsThrownIfScheduleDoesNotBelongToServer";d:0.042;s:86:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testSuccessfulMiddleware";d:0.044;s:102:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testSuccessfulMiddlewareWhenPatchRequest";d:0.041;s:104:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfUserTriesToEditSelf";d:0.044;s:123:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfSubuserServerDoesNotMatchRequestServer";d:0.041;s:101:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfNoSubuserIsFound";d:0.041;s:82:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testNoUserDefined";d:0.038;s:81:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testNonAdminUser";d:0.041;s:78:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testAdminUser";d:0.042;s:81:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithNoIPRestrictions";d:0.044;s:72:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithValidIP";d:0.045;s:84:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testValidIPAgainstCIDRRange";d:0.046;s:74:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithInvalidIP";d:0.04;s:89:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testMissingBearerTokenThrowsException";d:0.051;s:73:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testInvalidIdentifier";d:0.042;s:66:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testValidToken";d:0.046;s:77:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testValidTokenWithUserKey";d:0.042;s:74:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testAccessWithoutToken";d:0.042;s:81:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testInvalidTokenForIdentifier";d:0.041;s:67:"Tests\Unit\Http\Middleware\Api\SetSessionDriverTest::testMiddleware";d:0.039;s:108:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect";d:0.034;s:101:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenIsNotValid";d:0.113;s:100:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNodeIsNotFound";d:0.038;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #0";d:0.04;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #1";d:0.038;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #2";d:0.038;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #3";d:0.037;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #4";d:0.039;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #5";d:0.042;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #6";d:0.038;s:56:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testPowerAction";d:0.69;s:58:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testCommandAction";d:0.041;s:67:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testNextTaskQueuedIfExists";d:0.04;s:69:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testInvalidActionPassedToJob";d:0.041;s:83:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testScheduleMarkedAsDisabledDoesNotProcess";d:0.04;}}} \ No newline at end of file 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 ); From 756a21ff046237996e0bd41b8f81b9a26c1de87c Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Wed, 24 Jun 2020 20:38:13 -0700 Subject: [PATCH 28/63] Remove unused code --- .phpunit.result.cache | 2 +- app/Http/Kernel.php | 2 - .../Server/AuthenticateAsSubuser.php | 59 ----- app/Providers/RouteServiceProvider.php | 2 +- .../DaemonKeys/DaemonKeyCreationService.php | 87 ------- .../DaemonKeys/DaemonKeyProviderService.php | 121 --------- .../DaemonKeys/DaemonKeyUpdateService.php | 88 ------- .../Server/AuthenticateAsSubuserTest.php | 72 ------ .../AllocationDeletionServiceTest.php | 5 +- .../Allocations/AssignmentServiceTest.php | 27 ++- .../SetDefaultAllocationServiceTest.php | 156 ------------ .../DaemonKeyCreationServiceTest.php | 98 -------- .../DaemonKeyProviderServiceTest.php | 229 ------------------ .../DaemonKeys/DaemonKeyUpdateServiceTest.php | 83 ------- .../RevokeMultipleDaemonKeysServiceTest.php | 116 --------- 15 files changed, 21 insertions(+), 1126 deletions(-) delete mode 100644 app/Http/Middleware/Server/AuthenticateAsSubuser.php delete mode 100644 app/Services/DaemonKeys/DaemonKeyCreationService.php delete mode 100644 app/Services/DaemonKeys/DaemonKeyProviderService.php delete mode 100644 app/Services/DaemonKeys/DaemonKeyUpdateService.php delete mode 100644 tests/Unit/Http/Middleware/Server/AuthenticateAsSubuserTest.php delete mode 100644 tests/Unit/Services/Allocations/SetDefaultAllocationServiceTest.php delete mode 100644 tests/Unit/Services/DaemonKeys/DaemonKeyCreationServiceTest.php delete mode 100644 tests/Unit/Services/DaemonKeys/DaemonKeyProviderServiceTest.php delete mode 100644 tests/Unit/Services/DaemonKeys/DaemonKeyUpdateServiceTest.php delete mode 100644 tests/Unit/Services/DaemonKeys/RevokeMultipleDaemonKeysServiceTest.php diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 90c076199..ef7eacd08 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -C:37:"PHPUnit\Runner\DefaultTestResultCache":22007:{a:2:{s:7:"defects";a:42:{s:79:"Tests\Unit\Http\Controllers\Admin\StatisticsControllerTest::testIndexController";i:4;s:73:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testIndexController";i:4;s:74:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusController";i:4;s:96:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerNotInstalled";i:4;s:95:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerIsSuspended";i:4;s:103:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithServerConnectionException";i:4;s:94:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithRequestException";i:4;s:87:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWithout2FactorEnabled";i:4;s:84:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWith2FactorEnabled";i:4;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #0";i:4;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #1";i:4;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #0";i:4;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #1";i:4;s:82:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNoUserDefined";i:6;s:81:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNonAdminUser";i:6;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithInvalidIP";i:6;s:73:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidIdentifier";i:6;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidTokenForIdentifier";i:6;s:105:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldContinueIfRouteIsExempted";i:4;s:103:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoTokenIsProvided";i:4;s:99:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoNodeIsFound";i:4;s:93:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testSuccessfulMiddlewareProcess";i:4;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserDoesNotExist";i:6;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserIsNotAnAdmin";i:6;s:62:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedOutUser";i:6;s:76:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testValidDaemonConnection";i:4;s:81:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testIgnoredRouteShouldContinue";i:4;s:85:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testExceptionThrownIfMissingHeader";i:3;s:100:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsSuspended";i:6;s:103:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsNotInstalled";i:6;s:98:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testExceptionIsThrownIfNoTokenIsFound";i:6;s:107:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfNoDatabaseRecordFound";i:6;s:119:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfDatabaseServerDoesNotMatchCurrent";i:6;s:115:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testExceptionIsThrownIfScheduleDoesNotBelongToServer";i:6;s:123:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfSubuserServerDoesNotMatchRequestServer";i:6;s:101:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfNoSubuserIsFound";i:6;s:100:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNodeIsNotFound";i:5;s:56:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testPowerAction";i:4;s:58:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testCommandAction";i:4;s:67:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testNextTaskQueuedIfExists";i:4;s:69:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testInvalidActionPassedToJob";i:6;s:83:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testScheduleMarkedAsDisabledDoesNotProcess";i:4;}s:5:"times";a:172:{s:81:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testSmtpDriverSelection";d:0.692;s:98:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testSmtpDriverSelectionWithOptionsPassed";d:0.038;s:84:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPHPMailDriverSelection";d:0.046;s:84:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMailgunDriverSelection";d:0.05;s:101:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMailgunDriverSelectionWithOptionsPassed";d:0.037;s:85:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMandrillDriverSelection";d:0.052;s:102:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMandrillDriverSelectionWithOptionsPassed";d:0.038;s:85:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPostmarkDriverSelection";d:0.051;s:102:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPostmarkDriverSelectionWithOptionsPassed";d:0.037;s:77:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testLocationIsDeleted";d:0.106;s:93:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testLocationIsDeletedIfPassedInOption";d:0.042;s:106:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testInteractiveEnvironmentAllowsReAttemptingSearch";d:0.06;s:115:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testNonInteractiveEnvironmentThrowsErrorIfNoLocationIsFound";d:0.041;s:94:"Tests\Unit\Commands\Location\MakeLocationCommandTest::testLocationIsCreatedWithNoOptionsPassed";d:0.044;s:95:"Tests\Unit\Commands\Location\MakeLocationCommandTest::testLocationIsCreatedWhenOptionsArePassed";d:0.045;s:109:"Tests\Unit\Commands\Maintenance\CleanServiceBackupFilesCommandTest::testCommandCleansFilesMoreThan5MinutesOld";d:0.044;s:114:"Tests\Unit\Commands\Maintenance\CleanServiceBackupFilesCommandTest::testCommandDoesNotCleanFileLessThan5MinutesOld";d:0.039;s:77:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleIsQueued";d:0.062;s:94:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleWithNoTasksIsNotProcessed";d:0.042;s:127:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleWithTasksObjectThatIsNotInstanceOfCollectionIsNotProcessed";d:0.042;s:69:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendAction";d:0.149;s:74:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendWithFilters";d:0.05;s:79:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendWithEmptyOptions";d:0.043;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #0";d:0.043;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #1";d:0.038;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #2";d:0.038;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #3";d:0.038;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #4";d:0.038;s:72:"Tests\Unit\Commands\User\DeleteUserCommandTest::testCommandWithNoOptions";d:0.065;s:83:"Tests\Unit\Commands\User\DeleteUserCommandTest::testCommandWithInvalidInitialSearch";d:0.047;s:67:"Tests\Unit\Commands\User\DeleteUserCommandTest::testReSearchAbility";d:0.044;s:102:"Tests\Unit\Commands\User\DeleteUserCommandTest::testAnsweringNoToDeletionConfirmationWillNotDeleteUser";d:0.043;s:81:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithSingleResult";d:0.043;s:84:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithMultipleResults";d:0.045;s:78:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithNoResults";d:0.037;s:97:"Tests\Unit\Commands\User\DisableTwoFactorCommandTest::testTwoFactorIsDisabledWhenNoOptionIsPassed";d:0.041;s:95:"Tests\Unit\Commands\User\DisableTwoFactorCommandTest::testTwoFactorIsDisabledWhenOptionIsPassed";d:0.041;s:76:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithNoPassedOptions";d:0.053;s:77:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithNoPasswordOption";d:0.044;s:74:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithOptionsPassed";d:0.043;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #0";d:0.603;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #1";d:0.038;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #2";d:0.038;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #3";d:0.038;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #4";d:0.041;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #5";d:0.037;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #6";d:0.038;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #7";d:0.037;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #8";d:0.037;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #9";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #10";d:0.038;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #11";d:0.038;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #12";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #13";d:0.039;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #14";d:0.038;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #15";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #16";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #17";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #18";d:0.043;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #19";d:0.037;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #20";d:0.037;s:77:"Tests\Unit\Http\Controllers\Admin\DatabaseControllerTest::testIndexController";d:0.174;s:76:"Tests\Unit\Http\Controllers\Admin\DatabaseControllerTest::testViewController";d:0.047;s:57:"Tests\Unit\Http\Controllers\MailControllerTest::testIndex";d:0.052;s:79:"Tests\Unit\Http\Controllers\Admin\StatisticsControllerTest::testIndexController";d:0.145;s:73:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testIndexController";d:0.054;s:74:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusController";d:0.039;s:96:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerNotInstalled";d:0.04;s:95:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerIsSuspended";d:0.037;s:103:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithServerConnectionException";d:0.038;s:94:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithRequestException";d:0.04;s:87:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWithout2FactorEnabled";d:0.068;s:84:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWith2FactorEnabled";d:0.042;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #0";d:0.044;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #1";d:0.041;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #0";d:0.049;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #1";d:0.043;s:82:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNoUserDefined";d:0.044;s:81:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNonAdminUser";d:0.047;s:78:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testAdminUser";d:0.041;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithNoIPRestrictions";d:0.043;s:72:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithValidIP";d:0.044;s:84:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testValidIPAgainstCIDRRange";d:0.043;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithInvalidIP";d:0.04;s:89:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testMissingBearerTokenThrowsException";d:0.049;s:73:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidIdentifier";d:0.042;s:66:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testValidToken";d:0.045;s:77:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testValidTokenWithUserKey";d:0.044;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testAccessWithoutToken";d:0.042;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidTokenForIdentifier";d:0.041;s:105:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldContinueIfRouteIsExempted";d:0.05;s:103:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoTokenIsProvided";d:0.038;s:99:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoNodeIsFound";d:0.035;s:93:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testSuccessfulMiddlewareProcess";d:0.04;s:67:"Tests\Unit\Http\Middleware\API\SetSessionDriverTest::testMiddleware";d:0.039;s:76:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testAdminsAreAuthenticated";d:0.049;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserDoesNotExist";d:0.043;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserIsNotAnAdmin";d:0.041;s:61:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedInUser";d:0.04;s:62:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedOutUser";d:0.039;s:76:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testValidDaemonConnection";d:0.04;s:81:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testIgnoredRouteShouldContinue";d:0.037;s:85:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testExceptionThrownIfMissingHeader";d:0.038;s:76:"Tests\Unit\Http\Middleware\LanguageMiddlewareTest::testLanguageIsSetForGuest";d:0.044;s:89:"Tests\Unit\Http\Middleware\LanguageMiddlewareTest::testLanguageIsSetWithAuthenticatedUser";d:0.042;s:64:"Tests\Unit\Http\Middleware\MaintenanceMiddlewareTest::testHandle";d:0.047;s:81:"Tests\Unit\Http\Middleware\MaintenanceMiddlewareTest::testHandleInMaintenanceMode";d:0.047;s:89:"Tests\Unit\Http\Middleware\RedirectIfAuthenticatedTest::testAuthenticatedUserIsRedirected";d:0.047;s:95:"Tests\Unit\Http\Middleware\RedirectIfAuthenticatedTest::testNonAuthenticatedUserIsNotRedirected";d:0.037;s:85:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestMissingUser";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #0";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #1";d:0.045;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #2";d:0.043;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #3";d:0.041;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #4";d:0.04;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #5";d:0.041;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #6";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #7";d:0.041;s:95:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorRequirementDisabled";d:0.042;s:103:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorRequirementWithInvalidValue";d:0.041;s:118:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsAdminUserWith2FADisabled";d:0.043;s:117:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsAdminUserWith2FAEnabled";d:0.04;s:102:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsNonAdmin";d:0.05;s:115:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAllUsersAsUserWith2FADisabled";d:0.042;s:114:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAllUsersAsUserWith2FAEnabled";d:0.041;s:100:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsSuspended";d:0.052;s:103:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsNotInstalled";d:0.042;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #0";d:0.038;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #1";d:0.039;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #2";d:0.039;s:82:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testValidServerProcess";d:0.042;s:85:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testSuccessfulMiddleware";d:0.05;s:98:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testExceptionIsThrownIfNoTokenIsFound";d:0.043;s:87:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testSuccessfulMiddleware";d:0.045;s:107:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfNoDatabaseRecordFound";d:0.043;s:119:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfDatabaseServerDoesNotMatchCurrent";d:0.042;s:87:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testSuccessfulMiddleware";d:0.051;s:115:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testExceptionIsThrownIfScheduleDoesNotBelongToServer";d:0.042;s:86:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testSuccessfulMiddleware";d:0.044;s:102:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testSuccessfulMiddlewareWhenPatchRequest";d:0.041;s:104:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfUserTriesToEditSelf";d:0.044;s:123:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfSubuserServerDoesNotMatchRequestServer";d:0.041;s:101:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfNoSubuserIsFound";d:0.041;s:82:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testNoUserDefined";d:0.038;s:81:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testNonAdminUser";d:0.041;s:78:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testAdminUser";d:0.042;s:81:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithNoIPRestrictions";d:0.044;s:72:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithValidIP";d:0.045;s:84:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testValidIPAgainstCIDRRange";d:0.046;s:74:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithInvalidIP";d:0.04;s:89:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testMissingBearerTokenThrowsException";d:0.051;s:73:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testInvalidIdentifier";d:0.042;s:66:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testValidToken";d:0.046;s:77:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testValidTokenWithUserKey";d:0.042;s:74:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testAccessWithoutToken";d:0.042;s:81:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testInvalidTokenForIdentifier";d:0.041;s:67:"Tests\Unit\Http\Middleware\Api\SetSessionDriverTest::testMiddleware";d:0.039;s:108:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect";d:0.034;s:101:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenIsNotValid";d:0.113;s:100:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNodeIsNotFound";d:0.038;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #0";d:0.04;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #1";d:0.038;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #2";d:0.038;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #3";d:0.037;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #4";d:0.039;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #5";d:0.042;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #6";d:0.038;s:56:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testPowerAction";d:0.69;s:58:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testCommandAction";d:0.041;s:67:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testNextTaskQueuedIfExists";d:0.04;s:69:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testInvalidActionPassedToJob";d:0.041;s:83:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testScheduleMarkedAsDisabledDoesNotProcess";d:0.04;}}} \ No newline at end of file +C:37:"PHPUnit\Runner\DefaultTestResultCache":66049:{a:2:{s:7:"defects";a:153:{s:79:"Tests\Unit\Http\Controllers\Admin\StatisticsControllerTest::testIndexController";i:4;s:73:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testIndexController";i:4;s:74:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusController";i:4;s:96:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerNotInstalled";i:4;s:95:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerIsSuspended";i:4;s:103:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithServerConnectionException";i:4;s:94:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithRequestException";i:4;s:87:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWithout2FactorEnabled";i:4;s:84:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWith2FactorEnabled";i:4;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #0";i:4;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #1";i:4;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #0";i:4;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #1";i:4;s:82:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNoUserDefined";i:6;s:81:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNonAdminUser";i:6;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithInvalidIP";i:6;s:73:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidIdentifier";i:6;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidTokenForIdentifier";i:6;s:105:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldContinueIfRouteIsExempted";i:4;s:103:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoTokenIsProvided";i:4;s:99:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoNodeIsFound";i:4;s:93:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testSuccessfulMiddlewareProcess";i:4;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserDoesNotExist";i:6;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserIsNotAnAdmin";i:6;s:62:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedOutUser";i:6;s:76:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testValidDaemonConnection";i:4;s:81:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testIgnoredRouteShouldContinue";i:4;s:85:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testExceptionThrownIfMissingHeader";i:3;s:100:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsSuspended";i:6;s:103:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsNotInstalled";i:6;s:98:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testExceptionIsThrownIfNoTokenIsFound";i:6;s:107:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfNoDatabaseRecordFound";i:6;s:119:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfDatabaseServerDoesNotMatchCurrent";i:6;s:115:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testExceptionIsThrownIfScheduleDoesNotBelongToServer";i:6;s:123:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfSubuserServerDoesNotMatchRequestServer";i:6;s:101:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfNoSubuserIsFound";i:6;s:100:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNodeIsNotFound";i:5;s:56:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testPowerAction";i:4;s:58:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testCommandAction";i:4;s:67:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testNextTaskQueuedIfExists";i:4;s:69:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testInvalidActionPassedToJob";i:6;s:83:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testScheduleMarkedAsDisabledDoesNotProcess";i:4;s:100:"Tests\Unit\Services\Allocations\AllocationDeletionServiceTest::testExceptionThrownIfAssignedToServer";i:6;s:96:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testCIDRNotatedIPAddressOutsideRangeLimit";i:6;s:92:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testAllocationWithPortsExceedingLimit";i:6;s:78:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testInvalidPortProvided";i:6;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #0";i:6;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #1";i:6;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #2";i:6;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #3";i:6;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #4";i:6;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #5";i:6;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #6";i:6;s:105:"Tests\Unit\Services\Allocations\SetDefaultAllocationServiceTest::testAllocationIsUpdated with data set #0";i:4;s:105:"Tests\Unit\Services\Allocations\SetDefaultAllocationServiceTest::testAllocationIsUpdated with data set #1";i:4;s:114:"Tests\Unit\Services\Allocations\SetDefaultAllocationServiceTest::testAllocationNotBelongingToServerThrowsException";i:3;s:101:"Tests\Unit\Services\Allocations\SetDefaultAllocationServiceTest::testExceptionThrownByGuzzleIsHandled";i:4;s:105:"Tests\Unit\Services\DaemonKeys\DaemonKeyProviderServiceTest::testExceptionIsThrownIfUserDoesNotDeserveKey";i:6;s:95:"Tests\Unit\Services\DaemonKeys\RevokeMultipleDaemonKeysServiceTest::testSuccessfulKeyRevocation";i:4;s:110:"Tests\Unit\Services\DaemonKeys\RevokeMultipleDaemonKeysServiceTest::testExceptionThrownFromDaemonCallIsHandled";i:3;s:107:"Tests\Unit\Services\DaemonKeys\RevokeMultipleDaemonKeysServiceTest::testIgnoredExceptionsAreHandledProperly";i:4;s:80:"Tests\Unit\Services\Databases\DatabasePasswordServiceTest::testPasswordIsChanged";i:4;s:102:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testNonRandomFoundHost with data set #0";i:4;s:102:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testNonRandomFoundHost with data set #1";i:4;s:82:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testNonRandomNoHost";i:6;s:82:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testRandomFoundHost";i:4;s:79:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testRandomNoHost";i:6;s:107:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testServerOverDatabaseLimit with data set #0";i:3;s:107:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testServerOverDatabaseLimit with data set #1";i:3;s:84:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testFeatureNotEnabled";i:3;s:80:"Tests\Unit\Services\Eggs\EggConfigurationServiceTest::testCorrectArrayIsReturned";i:4;s:100:"Tests\Unit\Services\Eggs\EggConfigurationServiceTest::testFunctionHandlesIntegerPassedInPlaceOfModel";i:4;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #0";i:6;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #1";i:6;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #2";i:6;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #3";i:6;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #4";i:6;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #5";i:6;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #6";i:6;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #7";i:6;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #8";i:6;s:107:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testInvalidValidationRulesResultInException";i:6;s:106:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionNotCausedByBadRuleIsNotCaught";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #0";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #1";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #2";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #3";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #4";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #5";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #6";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #7";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #8";i:6;s:105:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testInvalidValidationRulesResultInException";i:6;s:104:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionNotCausedByBadRuleIsNotCaught";i:6;s:82:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testPanelVersionIsReturned";i:4;s:103:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testPanelVersionIsReturnedAsErrorIfNoKeyIsFound";i:4;s:83:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testDaemonVersionIsReturned";i:4;s:104:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testDaemonVersionIsReturnedAsErrorIfNoKeyIsFound";i:4;s:80:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testDiscordUrlIsReturned";i:4;s:130:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingPanelVersion with data set #0";i:4;s:130:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingPanelVersion with data set #1";i:4;s:130:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingPanelVersion with data set #2";i:4;s:131:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingDaemonVersion with data set #0";i:4;s:131:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingDaemonVersion with data set #1";i:4;s:131:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingDaemonVersion with data set #2";i:4;s:94:"Tests\Unit\Services\Nodes\NodeCreationServiceTest::testNodeIsCreatedAndDaemonSecretIsGenerated";i:4;s:98:"Tests\Unit\Services\Nodes\NodeDeletionServiceTest::testExceptionIsThrownIfServersAreAttachedToNode";i:6;s:88:"Tests\Unit\Services\Nodes\NodeUpdateServiceTest::testNodeIsUpdatedAndDaemonSecretIsReset";i:4;s:93:"Tests\Unit\Services\Nodes\NodeUpdateServiceTest::testNodeIsUpdatedAndDaemonSecretIsNotChanged";i:4;s:81:"Tests\Unit\Services\Nodes\NodeUpdateServiceTest::testExceptionRelatedToConnection";i:3;s:84:"Tests\Unit\Services\Nodes\NodeUpdateServiceTest::testExceptionNotRelatedToConnection";i:3;s:97:"Tests\Unit\Services\Packs\ExportPackServiceTest::testExceptionIsThrownIfZipArchiveCannotBeCreated";i:6;s:102:"Tests\Unit\Services\Servers\ReinstallServerServiceTest::testServerShouldBeReinstalledWhenModelIsPassed";i:4;s:105:"Tests\Unit\Services\Servers\ReinstallServerServiceTest::testServerShouldBeReinstalledWhenServerIdIsPassed";i:4;s:114:"Tests\Unit\Services\Servers\ReinstallServerServiceTest::testExceptionThrownByGuzzleShouldBeReRenderedAsDisplayable";i:4;s:121:"Tests\Unit\Services\Servers\ReinstallServerServiceTest::testExceptionNotThrownByGuzzleShouldNotBeTransformedToDisplayable";i:4;s:99:"Tests\Unit\Services\Servers\ServerConfigurationStructureServiceTest::testCorrectStructureIsReturned";i:3;s:116:"Tests\Unit\Services\Servers\ServerCreationServiceTest::testCreateShouldHitAllOfTheNecessaryServicesAndStoreTheServer";i:4;s:75:"Tests\Unit\Services\Servers\ServerCreationServiceTest::testDataIsAutoFilled";i:4;s:79:"Tests\Unit\Services\Servers\ServerCreationServiceTest::testAutoDeploymentObject";i:4;s:99:"Tests\Unit\Services\Servers\ServerCreationServiceTest::testExceptionShouldBeThrownIfTheRequestFails";i:3;s:81:"Tests\Unit\Services\Servers\ServerDeletionServiceTest::testForceParameterCanBeSet";i:4;s:89:"Tests\Unit\Services\Servers\ServerDeletionServiceTest::testServerCanBeDeletedWithoutForce";i:4;s:113:"Tests\Unit\Services\Servers\ServerDeletionServiceTest::testServerShouldBeDeletedEvenWhenFailureOccursIfForceIsSet";i:4;s:120:"Tests\Unit\Services\Servers\ServerDeletionServiceTest::testExceptionShouldBeThrownIfDaemonReturnsAnErrorAndForceIsNotSet";i:3;s:91:"Tests\Unit\Services\Servers\StartupModificationServiceTest::testStartupModifiedAsNormalUser";i:4;s:94:"Tests\Unit\Services\Servers\StartupModificationServiceTest::testStartupModificationAsAdminUser";i:4;s:105:"Tests\Unit\Services\Servers\SuspensionServiceTest::testFunctionShouldAcceptAnIntegerInPlaceOfAServerModel";i:4;s:98:"Tests\Unit\Services\Servers\SuspensionServiceTest::testServerShouldBeSuspendedWhenNoActionIsPassed";i:4;s:107:"Tests\Unit\Services\Servers\SuspensionServiceTest::testServerShouldBeUnsuspendedWhenUnsuspendActionIsPassed";i:4;s:123:"Tests\Unit\Services\Servers\SuspensionServiceTest::testNoActionShouldHappenIfServerIsAlreadyUnsuspendedAndActionIsUnsuspend";i:4;s:119:"Tests\Unit\Services\Servers\SuspensionServiceTest::testNoActionShouldHappenIfServerIsAlreadySuspendedAndActionIsSuspend";i:4;s:119:"Tests\Unit\Services\Servers\SuspensionServiceTest::testExceptionThrownByGuzzleShouldBeCaughtAndTransformedToDisplayable";i:4;s:96:"Tests\Unit\Services\Servers\SuspensionServiceTest::testExceptionShouldBeThrownIfActionIsNotValid";i:4;s:95:"Tests\Unit\Services\Subusers\PermissionCreationServiceTest::testPermissionsAreAssignedCorrectly";i:4;s:87:"Tests\Unit\Services\Subusers\SubuserCreationServiceTest::testAccountIsCreatedForNewUser";i:4;s:93:"Tests\Unit\Services\Subusers\SubuserCreationServiceTest::testExistingUserCanBeAddedAsASubuser";i:4;s:97:"Tests\Unit\Services\Subusers\SubuserCreationServiceTest::testExceptionIsThrownIfUserIsServerOwner";i:4;s:101:"Tests\Unit\Services\Subusers\SubuserCreationServiceTest::testExceptionIsThrownIfUserIsAlreadyASubuser";i:4;s:80:"Tests\Unit\Services\Subusers\SubuserUpdateServiceTest::testPermissionsAreUpdated";i:4;s:99:"Tests\Unit\Services\Subusers\SubuserUpdateServiceTest::testExceptionIsThrownIfDaemonConnectionFails";i:4;s:83:"Tests\Unit\Services\Users\ToggleTwoFactorServiceTest::testTwoFactorIsEnabledForUser";i:4;s:77:"Tests\Unit\Services\Users\ToggleTwoFactorServiceTest::testTwoFactorIsDisabled";i:4;s:89:"Tests\Unit\Services\Users\ToggleTwoFactorServiceTest::testTwoFactorRemainsDisabledForUser";i:4;s:91:"Tests\Unit\Services\Users\ToggleTwoFactorServiceTest::testExceptionIsThrownIfTokenIsInvalid";i:3;s:101:"Tests\Unit\Services\Users\UserDeletionServiceTest::testExceptionIsThrownIfServersAreAttachedToAccount";i:6;s:119:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserWithoutTouchingHasherIfNoPasswordPassed with data set #0";i:4;s:119:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserWithoutTouchingHasherIfNoPasswordPassed with data set #1";i:4;s:119:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserWithoutTouchingHasherIfNoPasswordPassed with data set #2";i:4;s:119:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserWithoutTouchingHasherIfNoPasswordPassed with data set #3";i:4;s:88:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserAndHashPasswordIfProvided";i:4;s:90:"Tests\Unit\Services\Users\UserUpdateServiceTest::testAdministrativeUserRevokingAdminStatus";i:4;s:89:"Tests\Unit\Services\Users\UserUpdateServiceTest::testNormalUserShouldNotRevokeAdminStatus";i:4;}s:5:"times";a:438:{s:81:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testSmtpDriverSelection";d:0.19;s:98:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testSmtpDriverSelectionWithOptionsPassed";d:0.037;s:84:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPHPMailDriverSelection";d:0.051;s:84:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMailgunDriverSelection";d:0.049;s:101:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMailgunDriverSelectionWithOptionsPassed";d:0.033;s:85:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMandrillDriverSelection";d:0.049;s:102:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMandrillDriverSelectionWithOptionsPassed";d:0.034;s:85:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPostmarkDriverSelection";d:0.06;s:102:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPostmarkDriverSelectionWithOptionsPassed";d:0.033;s:77:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testLocationIsDeleted";d:0.053;s:93:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testLocationIsDeletedIfPassedInOption";d:0.035;s:106:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testInteractiveEnvironmentAllowsReAttemptingSearch";d:0.06;s:115:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testNonInteractiveEnvironmentThrowsErrorIfNoLocationIsFound";d:0.036;s:94:"Tests\Unit\Commands\Location\MakeLocationCommandTest::testLocationIsCreatedWithNoOptionsPassed";d:0.038;s:95:"Tests\Unit\Commands\Location\MakeLocationCommandTest::testLocationIsCreatedWhenOptionsArePassed";d:0.037;s:109:"Tests\Unit\Commands\Maintenance\CleanServiceBackupFilesCommandTest::testCommandCleansFilesMoreThan5MinutesOld";d:0.044;s:114:"Tests\Unit\Commands\Maintenance\CleanServiceBackupFilesCommandTest::testCommandDoesNotCleanFileLessThan5MinutesOld";d:0.043;s:77:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleIsQueued";d:0.066;s:94:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleWithNoTasksIsNotProcessed";d:0.046;s:127:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleWithTasksObjectThatIsNotInstanceOfCollectionIsNotProcessed";d:0.042;s:69:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendAction";d:0.148;s:74:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendWithFilters";d:0.048;s:79:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendWithEmptyOptions";d:0.041;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #0";d:0.039;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #1";d:0.036;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #2";d:0.042;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #3";d:0.035;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #4";d:0.037;s:72:"Tests\Unit\Commands\User\DeleteUserCommandTest::testCommandWithNoOptions";d:0.061;s:83:"Tests\Unit\Commands\User\DeleteUserCommandTest::testCommandWithInvalidInitialSearch";d:0.038;s:67:"Tests\Unit\Commands\User\DeleteUserCommandTest::testReSearchAbility";d:0.04;s:102:"Tests\Unit\Commands\User\DeleteUserCommandTest::testAnsweringNoToDeletionConfirmationWillNotDeleteUser";d:0.039;s:81:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithSingleResult";d:0.038;s:84:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithMultipleResults";d:0.04;s:78:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithNoResults";d:0.035;s:97:"Tests\Unit\Commands\User\DisableTwoFactorCommandTest::testTwoFactorIsDisabledWhenNoOptionIsPassed";d:0.04;s:95:"Tests\Unit\Commands\User\DisableTwoFactorCommandTest::testTwoFactorIsDisabledWhenOptionIsPassed";d:0.039;s:76:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithNoPassedOptions";d:0.055;s:77:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithNoPasswordOption";d:0.04;s:74:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithOptionsPassed";d:0.039;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #0";d:0.043;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #1";d:0.035;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #2";d:0.035;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #3";d:0.034;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #4";d:0.034;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #5";d:0.035;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #6";d:0.036;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #7";d:0.036;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #8";d:0.035;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #9";d:0.036;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #10";d:0.038;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #11";d:0.035;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #12";d:0.034;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #13";d:0.036;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #14";d:0.036;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #15";d:0.035;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #16";d:0.036;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #17";d:0.034;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #18";d:0.035;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #19";d:0.046;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #20";d:0.037;s:77:"Tests\Unit\Http\Controllers\Admin\DatabaseControllerTest::testIndexController";d:0.133;s:76:"Tests\Unit\Http\Controllers\Admin\DatabaseControllerTest::testViewController";d:0.034;s:57:"Tests\Unit\Http\Controllers\MailControllerTest::testIndex";d:0.041;s:79:"Tests\Unit\Http\Controllers\Admin\StatisticsControllerTest::testIndexController";d:0.145;s:73:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testIndexController";d:0.054;s:74:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusController";d:0.039;s:96:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerNotInstalled";d:0.04;s:95:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerIsSuspended";d:0.037;s:103:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithServerConnectionException";d:0.038;s:94:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithRequestException";d:0.04;s:87:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWithout2FactorEnabled";d:0.068;s:84:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWith2FactorEnabled";d:0.042;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #0";d:0.044;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #1";d:0.041;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #0";d:0.049;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #1";d:0.043;s:82:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNoUserDefined";d:0.044;s:81:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNonAdminUser";d:0.047;s:78:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testAdminUser";d:0.041;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithNoIPRestrictions";d:0.043;s:72:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithValidIP";d:0.044;s:84:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testValidIPAgainstCIDRRange";d:0.043;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithInvalidIP";d:0.04;s:89:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testMissingBearerTokenThrowsException";d:0.049;s:73:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidIdentifier";d:0.042;s:66:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testValidToken";d:0.045;s:77:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testValidTokenWithUserKey";d:0.044;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testAccessWithoutToken";d:0.042;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidTokenForIdentifier";d:0.041;s:105:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldContinueIfRouteIsExempted";d:0.045;s:103:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoTokenIsProvided";d:0.035;s:99:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoNodeIsFound";d:0.035;s:93:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testSuccessfulMiddlewareProcess";d:0.037;s:67:"Tests\Unit\Http\Middleware\API\SetSessionDriverTest::testMiddleware";d:0.039;s:76:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testAdminsAreAuthenticated";d:0.035;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserDoesNotExist";d:0.04;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserIsNotAnAdmin";d:0.035;s:61:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedInUser";d:0.039;s:62:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedOutUser";d:0.039;s:76:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testValidDaemonConnection";d:0.04;s:81:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testIgnoredRouteShouldContinue";d:0.037;s:85:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testExceptionThrownIfMissingHeader";d:0.038;s:76:"Tests\Unit\Http\Middleware\LanguageMiddlewareTest::testLanguageIsSetForGuest";d:0.043;s:89:"Tests\Unit\Http\Middleware\LanguageMiddlewareTest::testLanguageIsSetWithAuthenticatedUser";d:0.042;s:64:"Tests\Unit\Http\Middleware\MaintenanceMiddlewareTest::testHandle";d:0.047;s:81:"Tests\Unit\Http\Middleware\MaintenanceMiddlewareTest::testHandleInMaintenanceMode";d:0.05;s:89:"Tests\Unit\Http\Middleware\RedirectIfAuthenticatedTest::testAuthenticatedUserIsRedirected";d:0.05;s:95:"Tests\Unit\Http\Middleware\RedirectIfAuthenticatedTest::testNonAuthenticatedUserIsNotRedirected";d:0.037;s:85:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestMissingUser";d:0.043;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #0";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #1";d:0.041;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #2";d:0.041;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #3";d:0.041;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #4";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #5";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #6";d:0.041;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #7";d:0.042;s:95:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorRequirementDisabled";d:0.042;s:103:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorRequirementWithInvalidValue";d:0.042;s:118:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsAdminUserWith2FADisabled";d:0.045;s:117:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsAdminUserWith2FAEnabled";d:0.041;s:102:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsNonAdmin";d:0.04;s:115:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAllUsersAsUserWith2FADisabled";d:0.042;s:114:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAllUsersAsUserWith2FAEnabled";d:0.041;s:100:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsSuspended";d:0.052;s:103:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsNotInstalled";d:0.043;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #0";d:0.038;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #1";d:0.037;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #2";d:0.037;s:82:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testValidServerProcess";d:0.041;s:85:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testSuccessfulMiddleware";d:0.053;s:98:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testExceptionIsThrownIfNoTokenIsFound";d:0.042;s:87:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testSuccessfulMiddleware";d:0.045;s:107:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfNoDatabaseRecordFound";d:0.043;s:119:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfDatabaseServerDoesNotMatchCurrent";d:0.042;s:87:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testSuccessfulMiddleware";d:0.051;s:115:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testExceptionIsThrownIfScheduleDoesNotBelongToServer";d:0.042;s:86:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testSuccessfulMiddleware";d:0.044;s:102:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testSuccessfulMiddlewareWhenPatchRequest";d:0.041;s:104:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfUserTriesToEditSelf";d:0.044;s:123:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfSubuserServerDoesNotMatchRequestServer";d:0.041;s:101:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfNoSubuserIsFound";d:0.041;s:82:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testNoUserDefined";d:0.034;s:81:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testNonAdminUser";d:0.037;s:78:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testAdminUser";d:0.036;s:81:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithNoIPRestrictions";d:0.038;s:72:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithValidIP";d:0.044;s:84:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testValidIPAgainstCIDRRange";d:0.042;s:74:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithInvalidIP";d:0.037;s:89:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testMissingBearerTokenThrowsException";d:0.046;s:73:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testInvalidIdentifier";d:0.041;s:66:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testValidToken";d:0.043;s:77:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testValidTokenWithUserKey";d:0.038;s:74:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testAccessWithoutToken";d:0.038;s:81:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testInvalidTokenForIdentifier";d:0.036;s:67:"Tests\Unit\Http\Middleware\Api\SetSessionDriverTest::testMiddleware";d:0.04;s:108:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect";d:0.034;s:101:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenIsNotValid";d:0.129;s:100:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNodeIsNotFound";d:0.035;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #0";d:0.035;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #1";d:0.034;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #2";d:0.034;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #3";d:0.036;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #4";d:0.037;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #5";d:0.038;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #6";d:0.034;s:56:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testPowerAction";d:0.072;s:58:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testCommandAction";d:0.042;s:67:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testNextTaskQueuedIfExists";d:0.038;s:69:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testInvalidActionPassedToJob";d:0.043;s:83:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testScheduleMarkedAsDisabledDoesNotProcess";d:0.037;s:51:"Tests\Unit\Rules\UsernameTest::testRuleIsStringable";d:0.534;s:66:"Tests\Unit\Rules\UsernameTest::testValidUsernames with data set #0";d:0.034;s:66:"Tests\Unit\Rules\UsernameTest::testValidUsernames with data set #1";d:0.032;s:66:"Tests\Unit\Rules\UsernameTest::testValidUsernames with data set #2";d:0.032;s:66:"Tests\Unit\Rules\UsernameTest::testValidUsernames with data set #3";d:0.032;s:66:"Tests\Unit\Rules\UsernameTest::testValidUsernames with data set #4";d:0.032;s:66:"Tests\Unit\Rules\UsernameTest::testValidUsernames with data set #5";d:0.033;s:66:"Tests\Unit\Rules\UsernameTest::testValidUsernames with data set #6";d:0.033;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #0";d:0.032;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #1";d:0.032;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #2";d:0.033;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #3";d:0.032;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #4";d:0.034;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #5";d:0.032;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #6";d:0.032;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #7";d:0.032;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #8";d:0.032;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #9";d:0.033;s:69:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #10";d:0.035;s:69:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #11";d:0.032;s:74:"Tests\Unit\Services\Acl\Api\AdminAclTest::testPermissions with data set #0";d:0.577;s:74:"Tests\Unit\Services\Acl\Api\AdminAclTest::testPermissions with data set #1";d:0.037;s:74:"Tests\Unit\Services\Acl\Api\AdminAclTest::testPermissions with data set #2";d:0.036;s:74:"Tests\Unit\Services\Acl\Api\AdminAclTest::testPermissions with data set #3";d:0.037;s:74:"Tests\Unit\Services\Acl\Api\AdminAclTest::testPermissions with data set #4";d:0.036;s:74:"Tests\Unit\Services\Acl\Api\AdminAclTest::testPermissions with data set #5";d:0.036;s:74:"Tests\Unit\Services\Acl\Api\AdminAclTest::testPermissions with data set #6";d:0.036;s:51:"Tests\Unit\Services\Acl\Api\AdminAclTest::testCheck";d:0.089;s:86:"Tests\Unit\Services\Allocations\AllocationDeletionServiceTest::testAllocationIsDeleted";d:0.722;s:100:"Tests\Unit\Services\Allocations\AllocationDeletionServiceTest::testExceptionThrownIfAssignedToServer";d:0.053;s:90:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testIndividualIpAddressWithoutRange";d:0.134;s:87:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testIndividualIpAddressWithRange";d:0.045;s:87:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testIndividualIPAddressWithAlias";d:0.044;s:93:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testDomainNamePassedInPlaceOfIPAddress";d:0.045;s:91:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testCIDRNotatedIPAddressWithoutRange";d:0.052;s:96:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testCIDRNotatedIPAddressOutsideRangeLimit";d:0.042;s:92:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testAllocationWithPortsExceedingLimit";d:0.042;s:78:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testInvalidPortProvided";d:0.042;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #0";d:0.04;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #1";d:0.039;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #2";d:0.038;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #3";d:0.038;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #4";d:0.039;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #5";d:0.038;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #6";d:0.041;s:105:"Tests\Unit\Services\Allocations\SetDefaultAllocationServiceTest::testAllocationIsUpdated with data set #0";d:0.055;s:105:"Tests\Unit\Services\Allocations\SetDefaultAllocationServiceTest::testAllocationIsUpdated with data set #1";d:0.038;s:114:"Tests\Unit\Services\Allocations\SetDefaultAllocationServiceTest::testAllocationNotBelongingToServerThrowsException";d:0.067;s:101:"Tests\Unit\Services\Allocations\SetDefaultAllocationServiceTest::testExceptionThrownByGuzzleIsHandled";d:0.047;s:64:"Tests\Unit\Services\Api\KeyCreationServiceTest::testKeyIsCreated";d:0.639;s:90:"Tests\Unit\Services\Api\KeyCreationServiceTest::testIdentifierAndTokenAreOnlySetByFunction";d:0.037;s:93:"Tests\Unit\Services\Api\KeyCreationServiceTest::testPermissionsAreRetrievedForApplicationKeys";d:0.038;s:116:"Tests\Unit\Services\Api\KeyCreationServiceTest::testPermissionsAreNotRetrievedForNonApplicationKeys with data set #0";d:0.039;s:116:"Tests\Unit\Services\Api\KeyCreationServiceTest::testPermissionsAreNotRetrievedForNonApplicationKeys with data set #1";d:0.039;s:116:"Tests\Unit\Services\Api\KeyCreationServiceTest::testPermissionsAreNotRetrievedForNonApplicationKeys with data set #2";d:0.038;s:116:"Tests\Unit\Services\Api\KeyCreationServiceTest::testPermissionsAreNotRetrievedForNonApplicationKeys with data set #3";d:0.038;s:83:"Tests\Unit\Services\DaemonKeys\DaemonKeyCreationServiceTest::testDaemonKeyIsCreated";d:0.701;s:78:"Tests\Unit\Services\DaemonKeys\DaemonKeyProviderServiceTest::testKeyIsReturned";d:0.106;s:84:"Tests\Unit\Services\DaemonKeys\DaemonKeyProviderServiceTest::testExpiredKeyIsUpdated";d:0.045;s:87:"Tests\Unit\Services\DaemonKeys\DaemonKeyProviderServiceTest::testExpiredKeyIsNotUpdated";d:0.043;s:95:"Tests\Unit\Services\DaemonKeys\DaemonKeyProviderServiceTest::testMissingKeyIsCreatedIfRootAdmin";d:0.047;s:103:"Tests\Unit\Services\DaemonKeys\DaemonKeyProviderServiceTest::testMissingKeyIsCreatedIfUserIsServerOwner";d:0.043;s:94:"Tests\Unit\Services\DaemonKeys\DaemonKeyProviderServiceTest::testMissingKeyIsCreatedForSubuser";d:0.043;s:105:"Tests\Unit\Services\DaemonKeys\DaemonKeyProviderServiceTest::testExceptionIsThrownIfUserDoesNotDeserveKey";d:0.046;s:75:"Tests\Unit\Services\DaemonKeys\DaemonKeyUpdateServiceTest::testKeyIsUpdated";d:0.044;s:95:"Tests\Unit\Services\DaemonKeys\RevokeMultipleDaemonKeysServiceTest::testSuccessfulKeyRevocation";d:0.132;s:110:"Tests\Unit\Services\DaemonKeys\RevokeMultipleDaemonKeysServiceTest::testExceptionThrownFromDaemonCallIsHandled";d:0.082;s:107:"Tests\Unit\Services\DaemonKeys\RevokeMultipleDaemonKeysServiceTest::testIgnoredExceptionsAreHandledProperly";d:0.045;s:80:"Tests\Unit\Services\Databases\DatabasePasswordServiceTest::testPasswordIsChanged";d:0.61;s:102:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testNonRandomFoundHost with data set #0";d:0.049;s:102:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testNonRandomFoundHost with data set #1";d:0.039;s:82:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testNonRandomNoHost";d:0.041;s:82:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testRandomFoundHost";d:0.037;s:79:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testRandomNoHost";d:0.036;s:107:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testServerOverDatabaseLimit with data set #0";d:0.066;s:107:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testServerOverDatabaseLimit with data set #1";d:0.037;s:84:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testFeatureNotEnabled";d:0.052;s:86:"Tests\Unit\Services\Databases\Hosts\HostCreationServiceTest::testDatabaseHostIsCreated";d:0.046;s:78:"Tests\Unit\Services\Databases\Hosts\HostDeletionServiceTest::testHostIsDeleted";d:0.034;s:126:"Tests\Unit\Services\Databases\Hosts\HostDeletionServiceTest::testExceptionIsThrownIfDeletingHostWithDatabases with data set #0";d:0.04;s:126:"Tests\Unit\Services\Databases\Hosts\HostDeletionServiceTest::testExceptionIsThrownIfDeletingHostWithDatabases with data set #1";d:0.034;s:126:"Tests\Unit\Services\Databases\Hosts\HostDeletionServiceTest::testExceptionIsThrownIfDeletingHostWithDatabases with data set #2";d:0.034;s:94:"Tests\Unit\Services\Databases\Hosts\HostUpdateServiceTest::testPasswordIsEncryptedWhenProvided";d:0.037;s:99:"Tests\Unit\Services\Databases\Hosts\HostUpdateServiceTest::testUpdateOccursWhenNoPasswordIsProvided";d:0.036;s:80:"Tests\Unit\Services\Eggs\EggConfigurationServiceTest::testCorrectArrayIsReturned";d:0.04;s:100:"Tests\Unit\Services\Eggs\EggConfigurationServiceTest::testFunctionHandlesIntegerPassedInPlaceOfModel";d:0.036;s:101:"Tests\Unit\Services\Services\Options\EggCreationServiceTest::testCreateNewModelWithoutUsingConfigFrom";d:0.047;s:94:"Tests\Unit\Services\Services\Options\EggCreationServiceTest::testCreateNewModelUsingConfigFrom";d:0.04;s:113:"Tests\Unit\Services\Services\Options\EggCreationServiceTest::testDataProvidedByHandlerTakesPriorityOverPassedData";d:0.039;s:112:"Tests\Unit\Services\Services\Options\EggCreationServiceTest::testExceptionIsThrownIfNoParentConfigurationIsFound";d:0.04;s:96:"Tests\Unit\Services\Services\Options\EggDeletionServiceTest::testEggIsDeletedIfNoServersAreFound";d:0.038;s:99:"Tests\Unit\Services\Services\Options\EggDeletionServiceTest::testExceptionIsThrownIfServersAreFound";d:0.037;s:102:"Tests\Unit\Services\Services\Options\EggDeletionServiceTest::testExceptionIsThrownIfChildrenArePresent";d:0.038;s:101:"Tests\Unit\Services\Services\Options\EggUpdateServiceTest::testEggIsUpdatedWhenNoConfigFromIsProvided";d:0.042;s:105:"Tests\Unit\Services\Services\Options\EggUpdateServiceTest::testOptionIsUpdatedWhenValidConfigFromIsPassed";d:0.041;s:109:"Tests\Unit\Services\Services\Options\EggUpdateServiceTest::testExceptionIsThrownIfInvalidParentConfigIsPassed";d:0.049;s:95:"Tests\Unit\Services\Services\Options\EggUpdateServiceTest::testIntegerCanBePassedInPlaceOfModel";d:0.038;s:105:"Tests\Unit\Services\Services\Options\InstallScriptServiceTest::testUpdateWithValidCopyScriptFromAttribute";d:0.042;s:107:"Tests\Unit\Services\Services\Options\InstallScriptServiceTest::testUpdateWithInvalidCopyScriptFromAttribute";d:0.044;s:106:"Tests\Unit\Services\Services\Options\InstallScriptServiceTest::testUpdateWithoutNewCopyScriptFromAttribute";d:0.038;s:103:"Tests\Unit\Services\Services\Options\InstallScriptServiceTest::testFunctionAcceptsIntegerInPlaceOfModel";d:0.039;s:84:"Tests\Unit\Services\Eggs\Sharing\EggExporterServiceTest::testJsonStructureIsExported";d:0.043;s:91:"Tests\Unit\Services\Services\Sharing\EggImporterServiceTest::testEggConfigurationIsImported";d:0.06;s:97:"Tests\Unit\Services\Services\Sharing\EggImporterServiceTest::testExceptionIsThrownIfFileIsInvalid";d:0.04;s:98:"Tests\Unit\Services\Services\Sharing\EggImporterServiceTest::testExceptionIsThrownIfFileIsNotAFile";d:0.036;s:105:"Tests\Unit\Services\Services\Sharing\EggImporterServiceTest::testExceptionIsThrownIfJsonMetaDataIsInvalid";d:0.037;s:101:"Tests\Unit\Services\Services\Sharing\EggImporterServiceTest::testExceptionIsThrownIfBadJsonIsProvided";d:0.039;s:79:"Tests\Unit\Services\Eggs\Sharing\EggUpdateImporterServiceTest::testEggIsUpdated";d:0.044;s:103:"Tests\Unit\Services\Eggs\Sharing\EggUpdateImporterServiceTest::testVariablesMissingFromImportAreDeleted";d:0.04;s:99:"Tests\Unit\Services\Eggs\Sharing\EggUpdateImporterServiceTest::testExceptionIsThrownIfFileIsInvalid";d:0.046;s:100:"Tests\Unit\Services\Eggs\Sharing\EggUpdateImporterServiceTest::testExceptionIsThrownIfFileIsNotAFile";d:0.036;s:107:"Tests\Unit\Services\Eggs\Sharing\EggUpdateImporterServiceTest::testExceptionIsThrownIfJsonMetaDataIsInvalid";d:0.036;s:103:"Tests\Unit\Services\Eggs\Sharing\EggUpdateImporterServiceTest::testExceptionIsThrownIfBadJsonIsProvided";d:0.043;s:94:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testVariableIsCreatedAndStored";d:0.043;s:108:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testOptionsPassedInArrayKeyAreParsedProperly";d:0.036;s:98:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testNullOptionValueIsPassedAsArray";d:0.036;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #0";d:0.038;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #1";d:0.037;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #2";d:0.036;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #3";d:0.037;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #4";d:0.036;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #5";d:0.036;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #6";d:0.038;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #7";d:0.037;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #8";d:0.035;s:97:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testEggIdPassedInDataIsNotApplied";d:0.036;s:107:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testInvalidValidationRulesResultInException";d:0.049;s:106:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionNotCausedByBadRuleIsNotCaught";d:0.036;s:116:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testVariableIsUpdatedWhenNoEnvironmentVariableIsPassed";d:0.04;s:82:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testNullDefaultValue";d:0.039;s:119:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testVariableIsUpdatedWhenValidEnvironmentVariableIsPassed";d:0.04;s:96:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testNullOptionValueIsPassedAsArray";d:0.039;s:116:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testDataPassedIntoHandlerTakesLowerPriorityThanDataSet";d:0.038;s:115:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsNotUnique";d:0.04;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #0";d:0.04;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #1";d:0.038;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #2";d:0.039;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #3";d:0.039;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #4";d:0.039;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #5";d:0.039;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #6";d:0.04;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #7";d:0.039;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #8";d:0.039;s:105:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testInvalidValidationRulesResultInException";d:0.041;s:104:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionNotCausedByBadRuleIsNotCaught";d:0.051;s:82:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testPanelVersionIsReturned";d:0.047;s:103:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testPanelVersionIsReturnedAsErrorIfNoKeyIsFound";d:0.039;s:83:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testDaemonVersionIsReturned";d:0.04;s:104:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testDaemonVersionIsReturnedAsErrorIfNoKeyIsFound";d:0.039;s:80:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testDiscordUrlIsReturned";d:0.039;s:130:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingPanelVersion with data set #0";d:0.038;s:130:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingPanelVersion with data set #1";d:0.037;s:130:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingPanelVersion with data set #2";d:0.037;s:131:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingDaemonVersion with data set #0";d:0.037;s:131:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingDaemonVersion with data set #1";d:0.038;s:131:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingDaemonVersion with data set #2";d:0.038;s:80:"Tests\Unit\Services\Locations\LocationCreationServiceTest::testLocationIsCreated";d:0.041;s:80:"Tests\Unit\Services\Locations\LocationDeletionServiceTest::testLocationIsDeleted";d:0.04;s:98:"Tests\Unit\Services\Locations\LocationDeletionServiceTest::testExceptionIsThrownIfNodesAreAttached";d:0.041;s:78:"Tests\Unit\Services\Locations\LocationUpdateServiceTest::testLocationIsUpdated";d:0.055;s:87:"Tests\Unit\Services\Locations\LocationUpdateServiceTest::testModelCanBePassedToFunction";d:0.042;s:74:"Tests\Unit\Services\Services\NestCreationServiceTest::testCreateNewService";d:0.045;s:88:"Tests\Unit\Services\Services\NestCreationServiceTest::testCreateServiceWithDefinedAuthor";d:0.042;s:74:"Tests\Unit\Services\Services\NestDeletionServiceTest::testServiceIsDeleted";d:0.041;s:112:"Tests\Unit\Services\Services\NestDeletionServiceTest::testExceptionIsThrownIfServersAreAttached with data set #0";d:0.04;s:112:"Tests\Unit\Services\Services\NestDeletionServiceTest::testExceptionIsThrownIfServersAreAttached with data set #1";d:0.039;s:112:"Tests\Unit\Services\Services\NestDeletionServiceTest::testExceptionIsThrownIfServersAreAttached with data set #2";d:0.039;s:112:"Tests\Unit\Services\Services\NestDeletionServiceTest::testExceptionIsThrownIfServersAreAttached with data set #3";d:0.039;s:87:"Tests\Unit\Services\Services\NestUpdateServiceTest::testAuthorArrayKeyIsRemovedIfPassed";d:0.041;s:95:"Tests\Unit\Services\Services\NestUpdateServiceTest::testServiceIsUpdatedWhenNoAuthorKeyIsPassed";d:0.041;s:94:"Tests\Unit\Services\Nodes\NodeCreationServiceTest::testNodeIsCreatedAndDaemonSecretIsGenerated";d:0.041;s:90:"Tests\Unit\Services\Nodes\NodeDeletionServiceTest::testNodeIsDeletedIfNoServersAreAttached";d:0.042;s:98:"Tests\Unit\Services\Nodes\NodeDeletionServiceTest::testExceptionIsThrownIfServersAreAttachedToNode";d:0.042;s:96:"Tests\Unit\Services\Nodes\NodeDeletionServiceTest::testModelCanBePassedToFunctionInPlaceOfNodeId";d:0.042;s:88:"Tests\Unit\Services\Nodes\NodeUpdateServiceTest::testNodeIsUpdatedAndDaemonSecretIsReset";d:0.061;s:93:"Tests\Unit\Services\Nodes\NodeUpdateServiceTest::testNodeIsUpdatedAndDaemonSecretIsNotChanged";d:0.043;s:81:"Tests\Unit\Services\Nodes\NodeUpdateServiceTest::testExceptionRelatedToConnection";d:0.05;s:84:"Tests\Unit\Services\Nodes\NodeUpdateServiceTest::testExceptionNotRelatedToConnection";d:0.044;s:88:"Tests\Unit\Services\Packs\ExportPackServiceTest::testFilesAreBundledIntoZipWhenRequested";d:0.06;s:87:"Tests\Unit\Services\Packs\ExportPackServiceTest::testPackConfigurationIsSavedAsJsonFile";d:0.052;s:84:"Tests\Unit\Services\Packs\ExportPackServiceTest::testPackIdCanBePassedInPlaceOfModel";d:0.047;s:97:"Tests\Unit\Services\Packs\ExportPackServiceTest::testExceptionIsThrownIfZipArchiveCannotBeCreated";d:0.045;s:94:"Tests\Unit\Services\Packs\PackCreationServiceTest::testPackIsCreatedWhenNoUploadedFileIsPassed";d:0.044;s:111:"Tests\Unit\Services\Packs\PackCreationServiceTest::testPackIsCreatedWhenUploadedFileIsProvided with data set #0";d:0.044;s:111:"Tests\Unit\Services\Packs\PackCreationServiceTest::testPackIsCreatedWhenUploadedFileIsProvided with data set #1";d:0.042;s:97:"Tests\Unit\Services\Packs\PackCreationServiceTest::testExceptionIsThrownIfInvalidUploadIsProvided";d:0.043;s:113:"Tests\Unit\Services\Packs\PackCreationServiceTest::testExceptionIsThrownIfInvalidMimetypeIsFound with data set #0";d:0.041;s:113:"Tests\Unit\Services\Packs\PackCreationServiceTest::testExceptionIsThrownIfInvalidMimetypeIsFound with data set #1";d:0.04;s:113:"Tests\Unit\Services\Packs\PackCreationServiceTest::testExceptionIsThrownIfInvalidMimetypeIsFound with data set #2";d:0.039;s:68:"Tests\Unit\Services\Packs\PackDeletionServiceTest::testPackIsDeleted";d:0.044;s:86:"Tests\Unit\Services\Packs\PackDeletionServiceTest::testPackIdCanBePassedInPlaceOfModel";d:0.042;s:96:"Tests\Unit\Services\Packs\PackDeletionServiceTest::testExceptionIsThrownIfServerIsAttachedToPack";d:0.042;s:66:"Tests\Unit\Services\Packs\PackUpdateServiceTest::testPackIsUpdated";d:0.045;s:108:"Tests\Unit\Services\Packs\PackUpdateServiceTest::testExceptionIsThrownIfModifyingEggIdWhenServersAreAttached";d:0.043;s:84:"Tests\Unit\Services\Packs\PackUpdateServiceTest::testPackIdCanBePassedInPlaceOfModel";d:0.043;s:93:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testJsonFileIsProcessed with data set #0";d:0.045;s:93:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testJsonFileIsProcessed with data set #1";d:0.042;s:75:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testZipfileIsProcessed";d:0.041;s:95:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfFileUploadIsInvalid";d:0.039;s:110:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfMimetypeIsInvalid with data set #0";d:0.039;s:110:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfMimetypeIsInvalid with data set #1";d:0.039;s:110:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfMimetypeIsInvalid with data set #2";d:0.039;s:98:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfZipArchiveIsUnreadable";d:0.041;s:121:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfZipDoesNotContainProperFiles with data set #0";d:0.041;s:121:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfZipDoesNotContainProperFiles with data set #1";d:0.052;s:121:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfZipDoesNotContainProperFiles with data set #2";d:0.039;s:107:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfArchiveCannotBeExtractedFromZip";d:0.046;s:85:"Tests\Unit\Services\Schedules\ProcessScheduleServiceTest::testScheduleIsUpdatedAndRun";d:0.057;s:94:"Tests\Unit\Services\Servers\EnvironmentServiceTest::testSettingEnvironmentKeyPersistsItInArray";d:0.041;s:112:"Tests\Unit\Services\Servers\EnvironmentServiceTest::testProcessShouldReturnDefaultEnvironmentVariablesForAServer";d:0.042;s:90:"Tests\Unit\Services\Servers\EnvironmentServiceTest::testProcessShouldReturnKeySetAtRuntime";d:0.043;s:115:"Tests\Unit\Services\Servers\EnvironmentServiceTest::testProcessShouldAllowOverwritingVariablesWithConfigurationFile";d:0.041;s:99:"Tests\Unit\Services\Servers\EnvironmentServiceTest::testVariablesSetInConfigurationAllowForClosures";d:0.041;s:120:"Tests\Unit\Services\Servers\EnvironmentServiceTest::testProcessShouldAllowOverwritingDefaultVariablesWithRuntimeProvided";d:0.042;s:102:"Tests\Unit\Services\Servers\ReinstallServerServiceTest::testServerShouldBeReinstalledWhenModelIsPassed";d:0.044;s:105:"Tests\Unit\Services\Servers\ReinstallServerServiceTest::testServerShouldBeReinstalledWhenServerIdIsPassed";d:0.042;s:114:"Tests\Unit\Services\Servers\ReinstallServerServiceTest::testExceptionThrownByGuzzleShouldBeReRenderedAsDisplayable";d:0.041;s:121:"Tests\Unit\Services\Servers\ReinstallServerServiceTest::testExceptionNotThrownByGuzzleShouldNotBeTransformedToDisplayable";d:0.044;s:99:"Tests\Unit\Services\Servers\ServerConfigurationStructureServiceTest::testCorrectStructureIsReturned";d:0.057;s:116:"Tests\Unit\Services\Servers\ServerCreationServiceTest::testCreateShouldHitAllOfTheNecessaryServicesAndStoreTheServer";d:0.062;s:75:"Tests\Unit\Services\Servers\ServerCreationServiceTest::testDataIsAutoFilled";d:0.052;s:79:"Tests\Unit\Services\Servers\ServerCreationServiceTest::testAutoDeploymentObject";d:0.048;s:99:"Tests\Unit\Services\Servers\ServerCreationServiceTest::testExceptionShouldBeThrownIfTheRequestFails";d:0.046;s:81:"Tests\Unit\Services\Servers\ServerDeletionServiceTest::testForceParameterCanBeSet";d:0.046;s:89:"Tests\Unit\Services\Servers\ServerDeletionServiceTest::testServerCanBeDeletedWithoutForce";d:0.045;s:113:"Tests\Unit\Services\Servers\ServerDeletionServiceTest::testServerShouldBeDeletedEvenWhenFailureOccursIfForceIsSet";d:0.044;s:120:"Tests\Unit\Services\Servers\ServerDeletionServiceTest::testExceptionShouldBeThrownIfDaemonReturnsAnErrorAndForceIsNotSet";d:0.043;s:78:"Tests\Unit\Services\Servers\StartupCommandViewServiceTest::testServiceResponse";d:0.045;s:91:"Tests\Unit\Services\Servers\StartupModificationServiceTest::testStartupModifiedAsNormalUser";d:0.044;s:94:"Tests\Unit\Services\Servers\StartupModificationServiceTest::testStartupModificationAsAdminUser";d:0.045;s:105:"Tests\Unit\Services\Servers\SuspensionServiceTest::testFunctionShouldAcceptAnIntegerInPlaceOfAServerModel";d:0.045;s:98:"Tests\Unit\Services\Servers\SuspensionServiceTest::testServerShouldBeSuspendedWhenNoActionIsPassed";d:0.058;s:107:"Tests\Unit\Services\Servers\SuspensionServiceTest::testServerShouldBeUnsuspendedWhenUnsuspendActionIsPassed";d:0.041;s:123:"Tests\Unit\Services\Servers\SuspensionServiceTest::testNoActionShouldHappenIfServerIsAlreadyUnsuspendedAndActionIsUnsuspend";d:0.042;s:119:"Tests\Unit\Services\Servers\SuspensionServiceTest::testNoActionShouldHappenIfServerIsAlreadySuspendedAndActionIsSuspend";d:0.042;s:119:"Tests\Unit\Services\Servers\SuspensionServiceTest::testExceptionThrownByGuzzleShouldBeCaughtAndTransformedToDisplayable";d:0.043;s:96:"Tests\Unit\Services\Servers\SuspensionServiceTest::testExceptionShouldBeThrownIfActionIsNotValid";d:0.041;s:113:"Tests\Unit\Services\Servers\VariableValidatorServiceTest::testEmptyResultSetShouldBeReturnedIfNoVariablesAreFound";d:0.038;s:140:"Tests\Unit\Services\Servers\VariableValidatorServiceTest::testValidatorShouldNotProcessVariablesSetAsNotUserEditableWhenAdminFlagIsNotPassed";d:0.044;s:114:"Tests\Unit\Services\Servers\VariableValidatorServiceTest::testValidatorShouldProcessAllVariablesWhenAdminFlagIsSet";d:0.044;s:124:"Tests\Unit\Services\Servers\VariableValidatorServiceTest::testValidatorShouldThrowExceptionWhenAValidationErrorIsEncountered";d:0.042;s:95:"Tests\Unit\Services\Subusers\PermissionCreationServiceTest::testPermissionsAreAssignedCorrectly";d:0.042;s:87:"Tests\Unit\Services\Subusers\SubuserCreationServiceTest::testAccountIsCreatedForNewUser";d:0.047;s:93:"Tests\Unit\Services\Subusers\SubuserCreationServiceTest::testExistingUserCanBeAddedAsASubuser";d:0.046;s:97:"Tests\Unit\Services\Subusers\SubuserCreationServiceTest::testExceptionIsThrownIfUserIsServerOwner";d:0.042;s:101:"Tests\Unit\Services\Subusers\SubuserCreationServiceTest::testExceptionIsThrownIfUserIsAlreadyASubuser";d:0.042;s:80:"Tests\Unit\Services\Subusers\SubuserUpdateServiceTest::testPermissionsAreUpdated";d:0.043;s:99:"Tests\Unit\Services\Subusers\SubuserUpdateServiceTest::testExceptionIsThrownIfDaemonConnectionFails";d:0.043;s:83:"Tests\Unit\Services\Users\ToggleTwoFactorServiceTest::testTwoFactorIsEnabledForUser";d:0.056;s:77:"Tests\Unit\Services\Users\ToggleTwoFactorServiceTest::testTwoFactorIsDisabled";d:0.044;s:89:"Tests\Unit\Services\Users\ToggleTwoFactorServiceTest::testTwoFactorRemainsDisabledForUser";d:0.043;s:91:"Tests\Unit\Services\Users\ToggleTwoFactorServiceTest::testExceptionIsThrownIfTokenIsInvalid";d:0.045;s:82:"Tests\Unit\Services\Users\TwoFactorSetupServiceTest::testSecretAndImageAreReturned";d:0.047;s:84:"Tests\Unit\Services\UserCreationServiceTest::testUserIsCreatedWhenPasswordIsProvided";d:0.055;s:74:"Tests\Unit\Services\UserCreationServiceTest::testUuidPassedInDataIsIgnored";d:0.044;s:86:"Tests\Unit\Services\UserCreationServiceTest::testUserIsCreatedWhenNoPasswordIsProvided";d:0.043;s:99:"Tests\Unit\Services\Users\UserDeletionServiceTest::testUserIsDeletedIfNoServersAreAttachedToAccount";d:0.042;s:101:"Tests\Unit\Services\Users\UserDeletionServiceTest::testExceptionIsThrownIfServersAreAttachedToAccount";d:0.044;s:86:"Tests\Unit\Services\Users\UserDeletionServiceTest::testModelCanBePassedInPlaceOfUserId";d:0.042;s:119:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserWithoutTouchingHasherIfNoPasswordPassed with data set #0";d:0.045;s:119:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserWithoutTouchingHasherIfNoPasswordPassed with data set #1";d:0.047;s:119:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserWithoutTouchingHasherIfNoPasswordPassed with data set #2";d:0.042;s:119:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserWithoutTouchingHasherIfNoPasswordPassed with data set #3";d:0.042;s:88:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserAndHashPasswordIfProvided";d:0.042;s:90:"Tests\Unit\Services\Users\UserUpdateServiceTest::testAdministrativeUserRevokingAdminStatus";d:0.041;s:89:"Tests\Unit\Services\Users\UserUpdateServiceTest::testNormalUserShouldNotRevokeAdminStatus";d:0.041;}}} \ No newline at end of file diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index ade6ff4a7..b211ae598 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -28,7 +28,6 @@ use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings; use Illuminate\Foundation\Http\Middleware\ValidatePostSize; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Pterodactyl\Http\Middleware\Server\AccessingValidServer; -use Pterodactyl\Http\Middleware\Server\AuthenticateAsSubuser; use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate; use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; @@ -101,7 +100,6 @@ class Kernel extends HttpKernel 'auth.basic' => AuthenticateWithBasicAuth::class, 'guest' => RedirectIfAuthenticated::class, 'server' => AccessingValidServer::class, - 'subuser.auth' => AuthenticateAsSubuser::class, 'admin' => AdminAuthenticate::class, 'csrf' => VerifyCsrfToken::class, 'throttle' => ThrottleRequests::class, diff --git a/app/Http/Middleware/Server/AuthenticateAsSubuser.php b/app/Http/Middleware/Server/AuthenticateAsSubuser.php deleted file mode 100644 index 06707117a..000000000 --- a/app/Http/Middleware/Server/AuthenticateAsSubuser.php +++ /dev/null @@ -1,59 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Http\Middleware\Server; - -use Closure; -use Illuminate\Http\Request; -use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; -use Pterodactyl\Exceptions\Repository\RecordNotFoundException; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; - -class AuthenticateAsSubuser -{ - /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService - */ - private $keyProviderService; - - /** - * SubuserAccessAuthenticate constructor. - * - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService - */ - public function __construct(DaemonKeyProviderService $keyProviderService) - { - $this->keyProviderService = $keyProviderService; - } - - /** - * Determine if a subuser has permissions to access a server, if so set their access token. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @return mixed - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException - */ - public function handle(Request $request, Closure $next) - { - $server = $request->attributes->get('server'); - - try { - $token = $this->keyProviderService->handle($server, $request->user()); - } catch (RecordNotFoundException $exception) { - throw new AccessDeniedHttpException('This account does not have permission to access this server.'); - } - - $request->attributes->set('server_token', $token); - - return $next($request); - } -} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 4e6099b9b..e6cb8b169 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -33,7 +33,7 @@ class RouteServiceProvider extends ServiceProvider ->namespace($this->namespace . '\Auth') ->group(base_path('routes/auth.php')); - Route::middleware(['web', 'csrf', 'auth', 'server', 'subuser.auth', 'node.maintenance']) + Route::middleware(['web', 'csrf', 'auth', 'server', 'node.maintenance']) ->prefix('/api/server/{server}') ->namespace($this->namespace . '\Server') ->group(base_path('routes/server.php')); diff --git a/app/Services/DaemonKeys/DaemonKeyCreationService.php b/app/Services/DaemonKeys/DaemonKeyCreationService.php deleted file mode 100644 index d2551324a..000000000 --- a/app/Services/DaemonKeys/DaemonKeyCreationService.php +++ /dev/null @@ -1,87 +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 Pterodactyl\Services\DaemonKeys; - -use Carbon\Carbon; -use Illuminate\Contracts\Config\Repository as ConfigRepository; -use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; - -class DaemonKeyCreationService -{ - /** - * @var \Carbon\Carbon - */ - protected $carbon; - - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - - /** - * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface - */ - protected $repository; - - /** - * DaemonKeyCreationService constructor. - * - * @param \Carbon\Carbon $carbon - * @param \Illuminate\Contracts\Config\Repository $config - * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository - */ - public function __construct( - Carbon $carbon, - ConfigRepository $config, - DaemonKeyRepositoryInterface $repository - ) { - $this->carbon = $carbon; - $this->config = $config; - $this->repository = $repository; - } - - /** - * Create a new daemon key to be used when connecting to a daemon. - * - * @param int $server - * @param int $user - * @return string - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - */ - public function handle(int $server, int $user) - { - $secret = DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . str_random(40); - - $this->repository->withoutFreshModel()->create([ - 'user_id' => $user, - 'server_id' => $server, - 'secret' => $secret, - 'expires_at' => $this->carbon->now()->addMinutes($this->config->get('pterodactyl.api.key_expire_time'))->toDateTimeString(), - ]); - - return $secret; - } -} diff --git a/app/Services/DaemonKeys/DaemonKeyProviderService.php b/app/Services/DaemonKeys/DaemonKeyProviderService.php deleted file mode 100644 index c875239d7..000000000 --- a/app/Services/DaemonKeys/DaemonKeyProviderService.php +++ /dev/null @@ -1,121 +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 Pterodactyl\Services\DaemonKeys; - -use Carbon\Carbon; -use Pterodactyl\Models\User; -use Pterodactyl\Models\Server; -use Pterodactyl\Exceptions\Repository\RecordNotFoundException; -use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface; -use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; - -class DaemonKeyProviderService -{ - /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService - */ - private $keyCreationService; - - /** - * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService - */ - private $keyUpdateService; - - /** - * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface - */ - private $subuserRepository; - - /** - * GetDaemonKeyService constructor. - * - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService $keyCreationService - * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository - * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService $keyUpdateService - * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $subuserRepository - */ - public function __construct( - DaemonKeyCreationService $keyCreationService, - DaemonKeyRepositoryInterface $repository, - DaemonKeyUpdateService $keyUpdateService, - SubuserRepositoryInterface $subuserRepository - ) { - $this->keyCreationService = $keyCreationService; - $this->keyUpdateService = $keyUpdateService; - $this->repository = $repository; - $this->subuserRepository = $subuserRepository; - } - - /** - * Get the access key for a user on a specific server. - * - * @param \Pterodactyl\Models\Server $server - * @param \Pterodactyl\Models\User $user - * @param bool $updateIfExpired - * @return string - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle(Server $server, User $user, $updateIfExpired = true): string - { - try { - $key = $this->repository->findFirstWhere([ - ['user_id', '=', $user->id], - ['server_id', '=', $server->id], - ]); - } catch (RecordNotFoundException $exception) { - // If key doesn't exist but we are an admin or the server owner, - // create it. - if ($user->root_admin || $user->id === $server->owner_id) { - return $this->keyCreationService->handle($server->id, $user->id); - } - - // Check if user is a subuser for this server. Ideally they should always have - // a record associated with them in the database, but we should still handle - // that potentiality here. - // - // If no subuser is found, a RecordNotFoundException will be thrown, thus handling - // the parent error as well. - $subuser = $this->subuserRepository->findFirstWhere([ - ['user_id', '=', $user->id], - ['server_id', '=', $server->id], - ]); - - return $this->keyCreationService->handle($subuser->server_id, $subuser->user_id); - } - - if (! $updateIfExpired || Carbon::now()->diffInSeconds($key->expires_at, false) > 0) { - return $key->secret; - } - - return $this->keyUpdateService->handle($key->id); - } -} diff --git a/app/Services/DaemonKeys/DaemonKeyUpdateService.php b/app/Services/DaemonKeys/DaemonKeyUpdateService.php deleted file mode 100644 index 91427f3de..000000000 --- a/app/Services/DaemonKeys/DaemonKeyUpdateService.php +++ /dev/null @@ -1,88 +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 Pterodactyl\Services\DaemonKeys; - -use Carbon\Carbon; -use Webmozart\Assert\Assert; -use Illuminate\Contracts\Config\Repository as ConfigRepository; -use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; - -class DaemonKeyUpdateService -{ - /** - * @var \Carbon\Carbon - */ - protected $carbon; - - /** - * @var \Illuminate\Contracts\Config\Repository - */ - protected $config; - - /** - * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface - */ - protected $repository; - - /** - * DaemonKeyUpdateService constructor. - * - * @param \Carbon\Carbon $carbon - * @param \Illuminate\Contracts\Config\Repository $config - * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository - */ - public function __construct( - Carbon $carbon, - ConfigRepository $config, - DaemonKeyRepositoryInterface $repository - ) { - $this->carbon = $carbon; - $this->config = $config; - $this->repository = $repository; - } - - /** - * Update a daemon key to expire the previous one. - * - * @param int $key - * @return string - * - * @throws \RuntimeException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle($key) - { - Assert::integerish($key, 'First argument passed to handle must be an integer, received %s.'); - - $secret = DaemonKeyRepositoryInterface::INTERNAL_KEY_IDENTIFIER . str_random(40); - $this->repository->withoutFreshModel()->update($key, [ - 'secret' => $secret, - 'expires_at' => $this->carbon->now()->addMinutes($this->config->get('pterodactyl.api.key_expire_time'))->toDateTimeString(), - ]); - - return $secret; - } -} diff --git a/tests/Unit/Http/Middleware/Server/AuthenticateAsSubuserTest.php b/tests/Unit/Http/Middleware/Server/AuthenticateAsSubuserTest.php deleted file mode 100644 index b9199a67a..000000000 --- a/tests/Unit/Http/Middleware/Server/AuthenticateAsSubuserTest.php +++ /dev/null @@ -1,72 +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. - */ - public function testExceptionIsThrownIfNoTokenIsFound() - { - $this->expectException(AccessDeniedHttpException::class); - $this->expectExceptionMessage('This account does not have permission to access this server.'); - - $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/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); - } -} From a5d9faf6b243a16f7bc7375a891abb87bbb53562 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Wed, 24 Jun 2020 20:47:52 -0700 Subject: [PATCH 29/63] Get database unit tests back into passing shape --- .gitignore | 1 + .phpunit.result.cache | 1 - .../Databases/DatabasePasswordServiceTest.php | 17 +++-- .../DeployServerDatabaseServiceTest.php | 74 +++---------------- 4 files changed, 20 insertions(+), 73 deletions(-) delete mode 100644 .phpunit.result.cache diff --git a/.gitignore b/.gitignore index 3352c6ad9..58250120e 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ coverage.xml resources/lang/locales.js resources/assets/pterodactyl/scripts/helpers/ziggy.js resources/assets/scripts/helpers/ziggy.js +.phpunit.result.cache diff --git a/.phpunit.result.cache b/.phpunit.result.cache deleted file mode 100644 index ef7eacd08..000000000 --- a/.phpunit.result.cache +++ /dev/null @@ -1 +0,0 @@ -C:37:"PHPUnit\Runner\DefaultTestResultCache":66049:{a:2:{s:7:"defects";a:153:{s:79:"Tests\Unit\Http\Controllers\Admin\StatisticsControllerTest::testIndexController";i:4;s:73:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testIndexController";i:4;s:74:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusController";i:4;s:96:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerNotInstalled";i:4;s:95:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerIsSuspended";i:4;s:103:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithServerConnectionException";i:4;s:94:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithRequestException";i:4;s:87:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWithout2FactorEnabled";i:4;s:84:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWith2FactorEnabled";i:4;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #0";i:4;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #1";i:4;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #0";i:4;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #1";i:4;s:82:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNoUserDefined";i:6;s:81:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNonAdminUser";i:6;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithInvalidIP";i:6;s:73:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidIdentifier";i:6;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidTokenForIdentifier";i:6;s:105:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldContinueIfRouteIsExempted";i:4;s:103:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoTokenIsProvided";i:4;s:99:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoNodeIsFound";i:4;s:93:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testSuccessfulMiddlewareProcess";i:4;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserDoesNotExist";i:6;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserIsNotAnAdmin";i:6;s:62:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedOutUser";i:6;s:76:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testValidDaemonConnection";i:4;s:81:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testIgnoredRouteShouldContinue";i:4;s:85:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testExceptionThrownIfMissingHeader";i:3;s:100:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsSuspended";i:6;s:103:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsNotInstalled";i:6;s:98:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testExceptionIsThrownIfNoTokenIsFound";i:6;s:107:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfNoDatabaseRecordFound";i:6;s:119:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfDatabaseServerDoesNotMatchCurrent";i:6;s:115:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testExceptionIsThrownIfScheduleDoesNotBelongToServer";i:6;s:123:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfSubuserServerDoesNotMatchRequestServer";i:6;s:101:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfNoSubuserIsFound";i:6;s:100:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNodeIsNotFound";i:5;s:56:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testPowerAction";i:4;s:58:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testCommandAction";i:4;s:67:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testNextTaskQueuedIfExists";i:4;s:69:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testInvalidActionPassedToJob";i:6;s:83:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testScheduleMarkedAsDisabledDoesNotProcess";i:4;s:100:"Tests\Unit\Services\Allocations\AllocationDeletionServiceTest::testExceptionThrownIfAssignedToServer";i:6;s:96:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testCIDRNotatedIPAddressOutsideRangeLimit";i:6;s:92:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testAllocationWithPortsExceedingLimit";i:6;s:78:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testInvalidPortProvided";i:6;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #0";i:6;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #1";i:6;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #2";i:6;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #3";i:6;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #4";i:6;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #5";i:6;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #6";i:6;s:105:"Tests\Unit\Services\Allocations\SetDefaultAllocationServiceTest::testAllocationIsUpdated with data set #0";i:4;s:105:"Tests\Unit\Services\Allocations\SetDefaultAllocationServiceTest::testAllocationIsUpdated with data set #1";i:4;s:114:"Tests\Unit\Services\Allocations\SetDefaultAllocationServiceTest::testAllocationNotBelongingToServerThrowsException";i:3;s:101:"Tests\Unit\Services\Allocations\SetDefaultAllocationServiceTest::testExceptionThrownByGuzzleIsHandled";i:4;s:105:"Tests\Unit\Services\DaemonKeys\DaemonKeyProviderServiceTest::testExceptionIsThrownIfUserDoesNotDeserveKey";i:6;s:95:"Tests\Unit\Services\DaemonKeys\RevokeMultipleDaemonKeysServiceTest::testSuccessfulKeyRevocation";i:4;s:110:"Tests\Unit\Services\DaemonKeys\RevokeMultipleDaemonKeysServiceTest::testExceptionThrownFromDaemonCallIsHandled";i:3;s:107:"Tests\Unit\Services\DaemonKeys\RevokeMultipleDaemonKeysServiceTest::testIgnoredExceptionsAreHandledProperly";i:4;s:80:"Tests\Unit\Services\Databases\DatabasePasswordServiceTest::testPasswordIsChanged";i:4;s:102:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testNonRandomFoundHost with data set #0";i:4;s:102:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testNonRandomFoundHost with data set #1";i:4;s:82:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testNonRandomNoHost";i:6;s:82:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testRandomFoundHost";i:4;s:79:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testRandomNoHost";i:6;s:107:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testServerOverDatabaseLimit with data set #0";i:3;s:107:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testServerOverDatabaseLimit with data set #1";i:3;s:84:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testFeatureNotEnabled";i:3;s:80:"Tests\Unit\Services\Eggs\EggConfigurationServiceTest::testCorrectArrayIsReturned";i:4;s:100:"Tests\Unit\Services\Eggs\EggConfigurationServiceTest::testFunctionHandlesIntegerPassedInPlaceOfModel";i:4;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #0";i:6;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #1";i:6;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #2";i:6;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #3";i:6;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #4";i:6;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #5";i:6;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #6";i:6;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #7";i:6;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #8";i:6;s:107:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testInvalidValidationRulesResultInException";i:6;s:106:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionNotCausedByBadRuleIsNotCaught";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #0";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #1";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #2";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #3";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #4";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #5";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #6";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #7";i:6;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #8";i:6;s:105:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testInvalidValidationRulesResultInException";i:6;s:104:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionNotCausedByBadRuleIsNotCaught";i:6;s:82:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testPanelVersionIsReturned";i:4;s:103:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testPanelVersionIsReturnedAsErrorIfNoKeyIsFound";i:4;s:83:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testDaemonVersionIsReturned";i:4;s:104:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testDaemonVersionIsReturnedAsErrorIfNoKeyIsFound";i:4;s:80:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testDiscordUrlIsReturned";i:4;s:130:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingPanelVersion with data set #0";i:4;s:130:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingPanelVersion with data set #1";i:4;s:130:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingPanelVersion with data set #2";i:4;s:131:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingDaemonVersion with data set #0";i:4;s:131:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingDaemonVersion with data set #1";i:4;s:131:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingDaemonVersion with data set #2";i:4;s:94:"Tests\Unit\Services\Nodes\NodeCreationServiceTest::testNodeIsCreatedAndDaemonSecretIsGenerated";i:4;s:98:"Tests\Unit\Services\Nodes\NodeDeletionServiceTest::testExceptionIsThrownIfServersAreAttachedToNode";i:6;s:88:"Tests\Unit\Services\Nodes\NodeUpdateServiceTest::testNodeIsUpdatedAndDaemonSecretIsReset";i:4;s:93:"Tests\Unit\Services\Nodes\NodeUpdateServiceTest::testNodeIsUpdatedAndDaemonSecretIsNotChanged";i:4;s:81:"Tests\Unit\Services\Nodes\NodeUpdateServiceTest::testExceptionRelatedToConnection";i:3;s:84:"Tests\Unit\Services\Nodes\NodeUpdateServiceTest::testExceptionNotRelatedToConnection";i:3;s:97:"Tests\Unit\Services\Packs\ExportPackServiceTest::testExceptionIsThrownIfZipArchiveCannotBeCreated";i:6;s:102:"Tests\Unit\Services\Servers\ReinstallServerServiceTest::testServerShouldBeReinstalledWhenModelIsPassed";i:4;s:105:"Tests\Unit\Services\Servers\ReinstallServerServiceTest::testServerShouldBeReinstalledWhenServerIdIsPassed";i:4;s:114:"Tests\Unit\Services\Servers\ReinstallServerServiceTest::testExceptionThrownByGuzzleShouldBeReRenderedAsDisplayable";i:4;s:121:"Tests\Unit\Services\Servers\ReinstallServerServiceTest::testExceptionNotThrownByGuzzleShouldNotBeTransformedToDisplayable";i:4;s:99:"Tests\Unit\Services\Servers\ServerConfigurationStructureServiceTest::testCorrectStructureIsReturned";i:3;s:116:"Tests\Unit\Services\Servers\ServerCreationServiceTest::testCreateShouldHitAllOfTheNecessaryServicesAndStoreTheServer";i:4;s:75:"Tests\Unit\Services\Servers\ServerCreationServiceTest::testDataIsAutoFilled";i:4;s:79:"Tests\Unit\Services\Servers\ServerCreationServiceTest::testAutoDeploymentObject";i:4;s:99:"Tests\Unit\Services\Servers\ServerCreationServiceTest::testExceptionShouldBeThrownIfTheRequestFails";i:3;s:81:"Tests\Unit\Services\Servers\ServerDeletionServiceTest::testForceParameterCanBeSet";i:4;s:89:"Tests\Unit\Services\Servers\ServerDeletionServiceTest::testServerCanBeDeletedWithoutForce";i:4;s:113:"Tests\Unit\Services\Servers\ServerDeletionServiceTest::testServerShouldBeDeletedEvenWhenFailureOccursIfForceIsSet";i:4;s:120:"Tests\Unit\Services\Servers\ServerDeletionServiceTest::testExceptionShouldBeThrownIfDaemonReturnsAnErrorAndForceIsNotSet";i:3;s:91:"Tests\Unit\Services\Servers\StartupModificationServiceTest::testStartupModifiedAsNormalUser";i:4;s:94:"Tests\Unit\Services\Servers\StartupModificationServiceTest::testStartupModificationAsAdminUser";i:4;s:105:"Tests\Unit\Services\Servers\SuspensionServiceTest::testFunctionShouldAcceptAnIntegerInPlaceOfAServerModel";i:4;s:98:"Tests\Unit\Services\Servers\SuspensionServiceTest::testServerShouldBeSuspendedWhenNoActionIsPassed";i:4;s:107:"Tests\Unit\Services\Servers\SuspensionServiceTest::testServerShouldBeUnsuspendedWhenUnsuspendActionIsPassed";i:4;s:123:"Tests\Unit\Services\Servers\SuspensionServiceTest::testNoActionShouldHappenIfServerIsAlreadyUnsuspendedAndActionIsUnsuspend";i:4;s:119:"Tests\Unit\Services\Servers\SuspensionServiceTest::testNoActionShouldHappenIfServerIsAlreadySuspendedAndActionIsSuspend";i:4;s:119:"Tests\Unit\Services\Servers\SuspensionServiceTest::testExceptionThrownByGuzzleShouldBeCaughtAndTransformedToDisplayable";i:4;s:96:"Tests\Unit\Services\Servers\SuspensionServiceTest::testExceptionShouldBeThrownIfActionIsNotValid";i:4;s:95:"Tests\Unit\Services\Subusers\PermissionCreationServiceTest::testPermissionsAreAssignedCorrectly";i:4;s:87:"Tests\Unit\Services\Subusers\SubuserCreationServiceTest::testAccountIsCreatedForNewUser";i:4;s:93:"Tests\Unit\Services\Subusers\SubuserCreationServiceTest::testExistingUserCanBeAddedAsASubuser";i:4;s:97:"Tests\Unit\Services\Subusers\SubuserCreationServiceTest::testExceptionIsThrownIfUserIsServerOwner";i:4;s:101:"Tests\Unit\Services\Subusers\SubuserCreationServiceTest::testExceptionIsThrownIfUserIsAlreadyASubuser";i:4;s:80:"Tests\Unit\Services\Subusers\SubuserUpdateServiceTest::testPermissionsAreUpdated";i:4;s:99:"Tests\Unit\Services\Subusers\SubuserUpdateServiceTest::testExceptionIsThrownIfDaemonConnectionFails";i:4;s:83:"Tests\Unit\Services\Users\ToggleTwoFactorServiceTest::testTwoFactorIsEnabledForUser";i:4;s:77:"Tests\Unit\Services\Users\ToggleTwoFactorServiceTest::testTwoFactorIsDisabled";i:4;s:89:"Tests\Unit\Services\Users\ToggleTwoFactorServiceTest::testTwoFactorRemainsDisabledForUser";i:4;s:91:"Tests\Unit\Services\Users\ToggleTwoFactorServiceTest::testExceptionIsThrownIfTokenIsInvalid";i:3;s:101:"Tests\Unit\Services\Users\UserDeletionServiceTest::testExceptionIsThrownIfServersAreAttachedToAccount";i:6;s:119:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserWithoutTouchingHasherIfNoPasswordPassed with data set #0";i:4;s:119:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserWithoutTouchingHasherIfNoPasswordPassed with data set #1";i:4;s:119:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserWithoutTouchingHasherIfNoPasswordPassed with data set #2";i:4;s:119:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserWithoutTouchingHasherIfNoPasswordPassed with data set #3";i:4;s:88:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserAndHashPasswordIfProvided";i:4;s:90:"Tests\Unit\Services\Users\UserUpdateServiceTest::testAdministrativeUserRevokingAdminStatus";i:4;s:89:"Tests\Unit\Services\Users\UserUpdateServiceTest::testNormalUserShouldNotRevokeAdminStatus";i:4;}s:5:"times";a:438:{s:81:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testSmtpDriverSelection";d:0.19;s:98:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testSmtpDriverSelectionWithOptionsPassed";d:0.037;s:84:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPHPMailDriverSelection";d:0.051;s:84:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMailgunDriverSelection";d:0.049;s:101:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMailgunDriverSelectionWithOptionsPassed";d:0.033;s:85:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMandrillDriverSelection";d:0.049;s:102:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testMandrillDriverSelectionWithOptionsPassed";d:0.034;s:85:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPostmarkDriverSelection";d:0.06;s:102:"Tests\Unit\Commands\Environment\EmailSettingsCommandTest::testPostmarkDriverSelectionWithOptionsPassed";d:0.033;s:77:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testLocationIsDeleted";d:0.053;s:93:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testLocationIsDeletedIfPassedInOption";d:0.035;s:106:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testInteractiveEnvironmentAllowsReAttemptingSearch";d:0.06;s:115:"Tests\Unit\Commands\Location\DeleteLocationCommandTest::testNonInteractiveEnvironmentThrowsErrorIfNoLocationIsFound";d:0.036;s:94:"Tests\Unit\Commands\Location\MakeLocationCommandTest::testLocationIsCreatedWithNoOptionsPassed";d:0.038;s:95:"Tests\Unit\Commands\Location\MakeLocationCommandTest::testLocationIsCreatedWhenOptionsArePassed";d:0.037;s:109:"Tests\Unit\Commands\Maintenance\CleanServiceBackupFilesCommandTest::testCommandCleansFilesMoreThan5MinutesOld";d:0.044;s:114:"Tests\Unit\Commands\Maintenance\CleanServiceBackupFilesCommandTest::testCommandDoesNotCleanFileLessThan5MinutesOld";d:0.043;s:77:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleIsQueued";d:0.066;s:94:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleWithNoTasksIsNotProcessed";d:0.046;s:127:"Tests\Unit\Commands\Schedule\ProcessRunnableCommandTest::testScheduleWithTasksObjectThatIsNotInstanceOfCollectionIsNotProcessed";d:0.042;s:69:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendAction";d:0.148;s:74:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendWithFilters";d:0.048;s:79:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testSendWithEmptyOptions";d:0.041;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #0";d:0.039;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #1";d:0.036;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #2";d:0.042;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #3";d:0.035;s:92:"Tests\Unit\Commands\Server\BulkPowerActionCommandTest::testValidationErrors with data set #4";d:0.037;s:72:"Tests\Unit\Commands\User\DeleteUserCommandTest::testCommandWithNoOptions";d:0.061;s:83:"Tests\Unit\Commands\User\DeleteUserCommandTest::testCommandWithInvalidInitialSearch";d:0.038;s:67:"Tests\Unit\Commands\User\DeleteUserCommandTest::testReSearchAbility";d:0.04;s:102:"Tests\Unit\Commands\User\DeleteUserCommandTest::testAnsweringNoToDeletionConfirmationWillNotDeleteUser";d:0.039;s:81:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithSingleResult";d:0.038;s:84:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithMultipleResults";d:0.04;s:78:"Tests\Unit\Commands\User\DeleteUserCommandTest::testNoInteractionWithNoResults";d:0.035;s:97:"Tests\Unit\Commands\User\DisableTwoFactorCommandTest::testTwoFactorIsDisabledWhenNoOptionIsPassed";d:0.04;s:95:"Tests\Unit\Commands\User\DisableTwoFactorCommandTest::testTwoFactorIsDisabledWhenOptionIsPassed";d:0.039;s:76:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithNoPassedOptions";d:0.055;s:77:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithNoPasswordOption";d:0.04;s:74:"Tests\Unit\Commands\User\MakeUserCommandTest::testCommandWithOptionsPassed";d:0.039;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #0";d:0.043;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #1";d:0.035;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #2";d:0.035;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #3";d:0.034;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #4";d:0.034;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #5";d:0.035;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #6";d:0.036;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #7";d:0.036;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #8";d:0.035;s:59:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #9";d:0.036;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #10";d:0.038;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #11";d:0.035;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #12";d:0.034;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #13";d:0.036;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #14";d:0.036;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #15";d:0.035;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #16";d:0.036;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #17";d:0.034;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #18";d:0.035;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #19";d:0.046;s:60:"Tests\Unit\Helpers\IsDigitTest::testHelper with data set #20";d:0.037;s:77:"Tests\Unit\Http\Controllers\Admin\DatabaseControllerTest::testIndexController";d:0.133;s:76:"Tests\Unit\Http\Controllers\Admin\DatabaseControllerTest::testViewController";d:0.034;s:57:"Tests\Unit\Http\Controllers\MailControllerTest::testIndex";d:0.041;s:79:"Tests\Unit\Http\Controllers\Admin\StatisticsControllerTest::testIndexController";d:0.145;s:73:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testIndexController";d:0.054;s:74:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusController";d:0.039;s:96:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerNotInstalled";d:0.04;s:95:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWhenServerIsSuspended";d:0.037;s:103:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithServerConnectionException";d:0.038;s:94:"Tests\Unit\Http\Controllers\Base\IndexControllerTest::testStatusControllerWithRequestException";d:0.04;s:87:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWithout2FactorEnabled";d:0.068;s:84:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testIndexWith2FactorEnabled";d:0.042;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #0";d:0.044;s:83:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStore with data set #1";d:0.041;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #0";d:0.049;s:108:"Tests\Unit\Http\Controllers\Base\SecurityControllerTest::testStoreWithInvalidTokenException with data set #1";d:0.043;s:82:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNoUserDefined";d:0.044;s:81:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testNonAdminUser";d:0.047;s:78:"Tests\Unit\Http\Middleware\API\Application\AuthenticateUserTest::testAdminUser";d:0.041;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithNoIPRestrictions";d:0.043;s:72:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithValidIP";d:0.044;s:84:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testValidIPAgainstCIDRRange";d:0.043;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateIPAccessTest::testWithInvalidIP";d:0.04;s:89:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testMissingBearerTokenThrowsException";d:0.049;s:73:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidIdentifier";d:0.042;s:66:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testValidToken";d:0.045;s:77:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testValidTokenWithUserKey";d:0.044;s:74:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testAccessWithoutToken";d:0.042;s:81:"Tests\Unit\Http\Middleware\API\AuthenticateKeyTest::testInvalidTokenForIdentifier";d:0.041;s:105:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldContinueIfRouteIsExempted";d:0.045;s:103:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoTokenIsProvided";d:0.035;s:99:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNoNodeIsFound";d:0.035;s:93:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testSuccessfulMiddlewareProcess";d:0.037;s:67:"Tests\Unit\Http\Middleware\API\SetSessionDriverTest::testMiddleware";d:0.039;s:76:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testAdminsAreAuthenticated";d:0.035;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserDoesNotExist";d:0.04;s:89:"Tests\Unit\Http\Middleware\AdminAuthenticateTest::testExceptionIsThrownIfUserIsNotAnAdmin";d:0.035;s:61:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedInUser";d:0.039;s:62:"Tests\Unit\Http\Middleware\AuthenticateTest::testLoggedOutUser";d:0.039;s:76:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testValidDaemonConnection";d:0.04;s:81:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testIgnoredRouteShouldContinue";d:0.037;s:85:"Tests\Unit\Http\Middleware\DaemonAuthenticateTest::testExceptionThrownIfMissingHeader";d:0.038;s:76:"Tests\Unit\Http\Middleware\LanguageMiddlewareTest::testLanguageIsSetForGuest";d:0.043;s:89:"Tests\Unit\Http\Middleware\LanguageMiddlewareTest::testLanguageIsSetWithAuthenticatedUser";d:0.042;s:64:"Tests\Unit\Http\Middleware\MaintenanceMiddlewareTest::testHandle";d:0.047;s:81:"Tests\Unit\Http\Middleware\MaintenanceMiddlewareTest::testHandleInMaintenanceMode";d:0.05;s:89:"Tests\Unit\Http\Middleware\RedirectIfAuthenticatedTest::testAuthenticatedUserIsRedirected";d:0.05;s:95:"Tests\Unit\Http\Middleware\RedirectIfAuthenticatedTest::testNonAuthenticatedUserIsNotRedirected";d:0.037;s:85:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestMissingUser";d:0.043;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #0";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #1";d:0.041;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #2";d:0.041;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #3";d:0.041;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #4";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #5";d:0.042;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #6";d:0.041;s:105:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testRequestOnIgnoredRoute with data set #7";d:0.042;s:95:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorRequirementDisabled";d:0.042;s:103:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorRequirementWithInvalidValue";d:0.042;s:118:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsAdminUserWith2FADisabled";d:0.045;s:117:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsAdminUserWith2FAEnabled";d:0.041;s:102:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAdminsAsNonAdmin";d:0.04;s:115:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAllUsersAsUserWith2FADisabled";d:0.042;s:114:"Tests\Unit\Http\Middleware\RequireTwoFactorAuthenticationTest::testTwoFactorEnabledForAllUsersAsUserWith2FAEnabled";d:0.041;s:100:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsSuspended";d:0.052;s:103:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testExceptionIsThrownIfServerIsNotInstalled";d:0.043;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #0";d:0.038;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #1";d:0.037;s:109:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testCorrectErrorPagesAreRendered with data set #2";d:0.037;s:82:"Tests\Unit\Http\Middleware\Server\AccessingValidServerTest::testValidServerProcess";d:0.041;s:85:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testSuccessfulMiddleware";d:0.053;s:98:"Tests\Unit\Http\Middleware\Server\AuthenticateAsSubuserTest::testExceptionIsThrownIfNoTokenIsFound";d:0.042;s:87:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testSuccessfulMiddleware";d:0.045;s:107:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfNoDatabaseRecordFound";d:0.043;s:119:"Tests\Unit\Http\Middleware\Server\DatabaseBelongsToServerTest::testExceptionIsThrownIfDatabaseServerDoesNotMatchCurrent";d:0.042;s:87:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testSuccessfulMiddleware";d:0.051;s:115:"Tests\Unit\Http\Middleware\Server\ScheduleBelongsToServerTest::testExceptionIsThrownIfScheduleDoesNotBelongToServer";d:0.042;s:86:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testSuccessfulMiddleware";d:0.044;s:102:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testSuccessfulMiddlewareWhenPatchRequest";d:0.041;s:104:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfUserTriesToEditSelf";d:0.044;s:123:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfSubuserServerDoesNotMatchRequestServer";d:0.041;s:101:"Tests\Unit\Http\Middleware\Server\SubuserBelongsToServerTest::testExceptionIsThrownIfNoSubuserIsFound";d:0.041;s:82:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testNoUserDefined";d:0.034;s:81:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testNonAdminUser";d:0.037;s:78:"Tests\Unit\Http\Middleware\Api\Application\AuthenticateUserTest::testAdminUser";d:0.036;s:81:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithNoIPRestrictions";d:0.038;s:72:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithValidIP";d:0.044;s:84:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testValidIPAgainstCIDRRange";d:0.042;s:74:"Tests\Unit\Http\Middleware\Api\AuthenticateIPAccessTest::testWithInvalidIP";d:0.037;s:89:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testMissingBearerTokenThrowsException";d:0.046;s:73:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testInvalidIdentifier";d:0.041;s:66:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testValidToken";d:0.043;s:77:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testValidTokenWithUserKey";d:0.038;s:74:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testAccessWithoutToken";d:0.038;s:81:"Tests\Unit\Http\Middleware\Api\AuthenticateKeyTest::testInvalidTokenForIdentifier";d:0.036;s:67:"Tests\Unit\Http\Middleware\Api\SetSessionDriverTest::testMiddleware";d:0.04;s:108:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect";d:0.034;s:101:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenIsNotValid";d:0.129;s:100:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfNodeIsNotFound";d:0.035;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #0";d:0.035;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #1";d:0.034;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #2";d:0.034;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #3";d:0.036;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #4";d:0.037;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #5";d:0.038;s:125:"Tests\Unit\Http\Middleware\Api\Daemon\DaemonAuthenticateTest::testResponseShouldFailIfTokenFormatIsIncorrect with data set #6";d:0.034;s:56:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testPowerAction";d:0.072;s:58:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testCommandAction";d:0.042;s:67:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testNextTaskQueuedIfExists";d:0.038;s:69:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testInvalidActionPassedToJob";d:0.043;s:83:"Tests\Unit\Jobs\Schedule\RunTaskJobTest::testScheduleMarkedAsDisabledDoesNotProcess";d:0.037;s:51:"Tests\Unit\Rules\UsernameTest::testRuleIsStringable";d:0.534;s:66:"Tests\Unit\Rules\UsernameTest::testValidUsernames with data set #0";d:0.034;s:66:"Tests\Unit\Rules\UsernameTest::testValidUsernames with data set #1";d:0.032;s:66:"Tests\Unit\Rules\UsernameTest::testValidUsernames with data set #2";d:0.032;s:66:"Tests\Unit\Rules\UsernameTest::testValidUsernames with data set #3";d:0.032;s:66:"Tests\Unit\Rules\UsernameTest::testValidUsernames with data set #4";d:0.032;s:66:"Tests\Unit\Rules\UsernameTest::testValidUsernames with data set #5";d:0.033;s:66:"Tests\Unit\Rules\UsernameTest::testValidUsernames with data set #6";d:0.033;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #0";d:0.032;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #1";d:0.032;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #2";d:0.033;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #3";d:0.032;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #4";d:0.034;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #5";d:0.032;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #6";d:0.032;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #7";d:0.032;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #8";d:0.032;s:68:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #9";d:0.033;s:69:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #10";d:0.035;s:69:"Tests\Unit\Rules\UsernameTest::testInvalidUsernames with data set #11";d:0.032;s:74:"Tests\Unit\Services\Acl\Api\AdminAclTest::testPermissions with data set #0";d:0.577;s:74:"Tests\Unit\Services\Acl\Api\AdminAclTest::testPermissions with data set #1";d:0.037;s:74:"Tests\Unit\Services\Acl\Api\AdminAclTest::testPermissions with data set #2";d:0.036;s:74:"Tests\Unit\Services\Acl\Api\AdminAclTest::testPermissions with data set #3";d:0.037;s:74:"Tests\Unit\Services\Acl\Api\AdminAclTest::testPermissions with data set #4";d:0.036;s:74:"Tests\Unit\Services\Acl\Api\AdminAclTest::testPermissions with data set #5";d:0.036;s:74:"Tests\Unit\Services\Acl\Api\AdminAclTest::testPermissions with data set #6";d:0.036;s:51:"Tests\Unit\Services\Acl\Api\AdminAclTest::testCheck";d:0.089;s:86:"Tests\Unit\Services\Allocations\AllocationDeletionServiceTest::testAllocationIsDeleted";d:0.722;s:100:"Tests\Unit\Services\Allocations\AllocationDeletionServiceTest::testExceptionThrownIfAssignedToServer";d:0.053;s:90:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testIndividualIpAddressWithoutRange";d:0.134;s:87:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testIndividualIpAddressWithRange";d:0.045;s:87:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testIndividualIPAddressWithAlias";d:0.044;s:93:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testDomainNamePassedInPlaceOfIPAddress";d:0.045;s:91:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testCIDRNotatedIPAddressWithoutRange";d:0.052;s:96:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testCIDRNotatedIPAddressOutsideRangeLimit";d:0.042;s:92:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testAllocationWithPortsExceedingLimit";d:0.042;s:78:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testInvalidPortProvided";d:0.042;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #0";d:0.04;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #1";d:0.039;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #2";d:0.038;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #3";d:0.038;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #4";d:0.039;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #5";d:0.038;s:105:"Tests\Unit\Services\Allocations\AssignmentServiceTest::testPortRangeOutsideOfRangeLimits with data set #6";d:0.041;s:105:"Tests\Unit\Services\Allocations\SetDefaultAllocationServiceTest::testAllocationIsUpdated with data set #0";d:0.055;s:105:"Tests\Unit\Services\Allocations\SetDefaultAllocationServiceTest::testAllocationIsUpdated with data set #1";d:0.038;s:114:"Tests\Unit\Services\Allocations\SetDefaultAllocationServiceTest::testAllocationNotBelongingToServerThrowsException";d:0.067;s:101:"Tests\Unit\Services\Allocations\SetDefaultAllocationServiceTest::testExceptionThrownByGuzzleIsHandled";d:0.047;s:64:"Tests\Unit\Services\Api\KeyCreationServiceTest::testKeyIsCreated";d:0.639;s:90:"Tests\Unit\Services\Api\KeyCreationServiceTest::testIdentifierAndTokenAreOnlySetByFunction";d:0.037;s:93:"Tests\Unit\Services\Api\KeyCreationServiceTest::testPermissionsAreRetrievedForApplicationKeys";d:0.038;s:116:"Tests\Unit\Services\Api\KeyCreationServiceTest::testPermissionsAreNotRetrievedForNonApplicationKeys with data set #0";d:0.039;s:116:"Tests\Unit\Services\Api\KeyCreationServiceTest::testPermissionsAreNotRetrievedForNonApplicationKeys with data set #1";d:0.039;s:116:"Tests\Unit\Services\Api\KeyCreationServiceTest::testPermissionsAreNotRetrievedForNonApplicationKeys with data set #2";d:0.038;s:116:"Tests\Unit\Services\Api\KeyCreationServiceTest::testPermissionsAreNotRetrievedForNonApplicationKeys with data set #3";d:0.038;s:83:"Tests\Unit\Services\DaemonKeys\DaemonKeyCreationServiceTest::testDaemonKeyIsCreated";d:0.701;s:78:"Tests\Unit\Services\DaemonKeys\DaemonKeyProviderServiceTest::testKeyIsReturned";d:0.106;s:84:"Tests\Unit\Services\DaemonKeys\DaemonKeyProviderServiceTest::testExpiredKeyIsUpdated";d:0.045;s:87:"Tests\Unit\Services\DaemonKeys\DaemonKeyProviderServiceTest::testExpiredKeyIsNotUpdated";d:0.043;s:95:"Tests\Unit\Services\DaemonKeys\DaemonKeyProviderServiceTest::testMissingKeyIsCreatedIfRootAdmin";d:0.047;s:103:"Tests\Unit\Services\DaemonKeys\DaemonKeyProviderServiceTest::testMissingKeyIsCreatedIfUserIsServerOwner";d:0.043;s:94:"Tests\Unit\Services\DaemonKeys\DaemonKeyProviderServiceTest::testMissingKeyIsCreatedForSubuser";d:0.043;s:105:"Tests\Unit\Services\DaemonKeys\DaemonKeyProviderServiceTest::testExceptionIsThrownIfUserDoesNotDeserveKey";d:0.046;s:75:"Tests\Unit\Services\DaemonKeys\DaemonKeyUpdateServiceTest::testKeyIsUpdated";d:0.044;s:95:"Tests\Unit\Services\DaemonKeys\RevokeMultipleDaemonKeysServiceTest::testSuccessfulKeyRevocation";d:0.132;s:110:"Tests\Unit\Services\DaemonKeys\RevokeMultipleDaemonKeysServiceTest::testExceptionThrownFromDaemonCallIsHandled";d:0.082;s:107:"Tests\Unit\Services\DaemonKeys\RevokeMultipleDaemonKeysServiceTest::testIgnoredExceptionsAreHandledProperly";d:0.045;s:80:"Tests\Unit\Services\Databases\DatabasePasswordServiceTest::testPasswordIsChanged";d:0.61;s:102:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testNonRandomFoundHost with data set #0";d:0.049;s:102:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testNonRandomFoundHost with data set #1";d:0.039;s:82:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testNonRandomNoHost";d:0.041;s:82:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testRandomFoundHost";d:0.037;s:79:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testRandomNoHost";d:0.036;s:107:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testServerOverDatabaseLimit with data set #0";d:0.066;s:107:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testServerOverDatabaseLimit with data set #1";d:0.037;s:84:"Tests\Unit\Services\Databases\DeployServerDatabaseServiceTest::testFeatureNotEnabled";d:0.052;s:86:"Tests\Unit\Services\Databases\Hosts\HostCreationServiceTest::testDatabaseHostIsCreated";d:0.046;s:78:"Tests\Unit\Services\Databases\Hosts\HostDeletionServiceTest::testHostIsDeleted";d:0.034;s:126:"Tests\Unit\Services\Databases\Hosts\HostDeletionServiceTest::testExceptionIsThrownIfDeletingHostWithDatabases with data set #0";d:0.04;s:126:"Tests\Unit\Services\Databases\Hosts\HostDeletionServiceTest::testExceptionIsThrownIfDeletingHostWithDatabases with data set #1";d:0.034;s:126:"Tests\Unit\Services\Databases\Hosts\HostDeletionServiceTest::testExceptionIsThrownIfDeletingHostWithDatabases with data set #2";d:0.034;s:94:"Tests\Unit\Services\Databases\Hosts\HostUpdateServiceTest::testPasswordIsEncryptedWhenProvided";d:0.037;s:99:"Tests\Unit\Services\Databases\Hosts\HostUpdateServiceTest::testUpdateOccursWhenNoPasswordIsProvided";d:0.036;s:80:"Tests\Unit\Services\Eggs\EggConfigurationServiceTest::testCorrectArrayIsReturned";d:0.04;s:100:"Tests\Unit\Services\Eggs\EggConfigurationServiceTest::testFunctionHandlesIntegerPassedInPlaceOfModel";d:0.036;s:101:"Tests\Unit\Services\Services\Options\EggCreationServiceTest::testCreateNewModelWithoutUsingConfigFrom";d:0.047;s:94:"Tests\Unit\Services\Services\Options\EggCreationServiceTest::testCreateNewModelUsingConfigFrom";d:0.04;s:113:"Tests\Unit\Services\Services\Options\EggCreationServiceTest::testDataProvidedByHandlerTakesPriorityOverPassedData";d:0.039;s:112:"Tests\Unit\Services\Services\Options\EggCreationServiceTest::testExceptionIsThrownIfNoParentConfigurationIsFound";d:0.04;s:96:"Tests\Unit\Services\Services\Options\EggDeletionServiceTest::testEggIsDeletedIfNoServersAreFound";d:0.038;s:99:"Tests\Unit\Services\Services\Options\EggDeletionServiceTest::testExceptionIsThrownIfServersAreFound";d:0.037;s:102:"Tests\Unit\Services\Services\Options\EggDeletionServiceTest::testExceptionIsThrownIfChildrenArePresent";d:0.038;s:101:"Tests\Unit\Services\Services\Options\EggUpdateServiceTest::testEggIsUpdatedWhenNoConfigFromIsProvided";d:0.042;s:105:"Tests\Unit\Services\Services\Options\EggUpdateServiceTest::testOptionIsUpdatedWhenValidConfigFromIsPassed";d:0.041;s:109:"Tests\Unit\Services\Services\Options\EggUpdateServiceTest::testExceptionIsThrownIfInvalidParentConfigIsPassed";d:0.049;s:95:"Tests\Unit\Services\Services\Options\EggUpdateServiceTest::testIntegerCanBePassedInPlaceOfModel";d:0.038;s:105:"Tests\Unit\Services\Services\Options\InstallScriptServiceTest::testUpdateWithValidCopyScriptFromAttribute";d:0.042;s:107:"Tests\Unit\Services\Services\Options\InstallScriptServiceTest::testUpdateWithInvalidCopyScriptFromAttribute";d:0.044;s:106:"Tests\Unit\Services\Services\Options\InstallScriptServiceTest::testUpdateWithoutNewCopyScriptFromAttribute";d:0.038;s:103:"Tests\Unit\Services\Services\Options\InstallScriptServiceTest::testFunctionAcceptsIntegerInPlaceOfModel";d:0.039;s:84:"Tests\Unit\Services\Eggs\Sharing\EggExporterServiceTest::testJsonStructureIsExported";d:0.043;s:91:"Tests\Unit\Services\Services\Sharing\EggImporterServiceTest::testEggConfigurationIsImported";d:0.06;s:97:"Tests\Unit\Services\Services\Sharing\EggImporterServiceTest::testExceptionIsThrownIfFileIsInvalid";d:0.04;s:98:"Tests\Unit\Services\Services\Sharing\EggImporterServiceTest::testExceptionIsThrownIfFileIsNotAFile";d:0.036;s:105:"Tests\Unit\Services\Services\Sharing\EggImporterServiceTest::testExceptionIsThrownIfJsonMetaDataIsInvalid";d:0.037;s:101:"Tests\Unit\Services\Services\Sharing\EggImporterServiceTest::testExceptionIsThrownIfBadJsonIsProvided";d:0.039;s:79:"Tests\Unit\Services\Eggs\Sharing\EggUpdateImporterServiceTest::testEggIsUpdated";d:0.044;s:103:"Tests\Unit\Services\Eggs\Sharing\EggUpdateImporterServiceTest::testVariablesMissingFromImportAreDeleted";d:0.04;s:99:"Tests\Unit\Services\Eggs\Sharing\EggUpdateImporterServiceTest::testExceptionIsThrownIfFileIsInvalid";d:0.046;s:100:"Tests\Unit\Services\Eggs\Sharing\EggUpdateImporterServiceTest::testExceptionIsThrownIfFileIsNotAFile";d:0.036;s:107:"Tests\Unit\Services\Eggs\Sharing\EggUpdateImporterServiceTest::testExceptionIsThrownIfJsonMetaDataIsInvalid";d:0.036;s:103:"Tests\Unit\Services\Eggs\Sharing\EggUpdateImporterServiceTest::testExceptionIsThrownIfBadJsonIsProvided";d:0.043;s:94:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testVariableIsCreatedAndStored";d:0.043;s:108:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testOptionsPassedInArrayKeyAreParsedProperly";d:0.036;s:98:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testNullOptionValueIsPassedAsArray";d:0.036;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #0";d:0.038;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #1";d:0.037;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #2";d:0.036;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #3";d:0.037;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #4";d:0.036;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #5";d:0.036;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #6";d:0.038;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #7";d:0.037;s:146:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #8";d:0.035;s:97:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testEggIdPassedInDataIsNotApplied";d:0.036;s:107:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testInvalidValidationRulesResultInException";d:0.049;s:106:"Tests\Unit\Services\Eggs\Variables\VariableCreationServiceTest::testExceptionNotCausedByBadRuleIsNotCaught";d:0.036;s:116:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testVariableIsUpdatedWhenNoEnvironmentVariableIsPassed";d:0.04;s:82:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testNullDefaultValue";d:0.039;s:119:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testVariableIsUpdatedWhenValidEnvironmentVariableIsPassed";d:0.04;s:96:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testNullOptionValueIsPassedAsArray";d:0.039;s:116:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testDataPassedIntoHandlerTakesLowerPriorityThanDataSet";d:0.038;s:115:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsNotUnique";d:0.04;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #0";d:0.04;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #1";d:0.038;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #2";d:0.039;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #3";d:0.039;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #4";d:0.039;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #5";d:0.039;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #6";d:0.04;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #7";d:0.039;s:144:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionIsThrownIfEnvironmentVariableIsInListOfReservedNames with data set #8";d:0.039;s:105:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testInvalidValidationRulesResultInException";d:0.041;s:104:"Tests\Unit\Services\Eggs\Variables\VariableUpdateServiceTest::testExceptionNotCausedByBadRuleIsNotCaught";d:0.051;s:82:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testPanelVersionIsReturned";d:0.047;s:103:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testPanelVersionIsReturnedAsErrorIfNoKeyIsFound";d:0.039;s:83:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testDaemonVersionIsReturned";d:0.04;s:104:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testDaemonVersionIsReturnedAsErrorIfNoKeyIsFound";d:0.039;s:80:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testDiscordUrlIsReturned";d:0.039;s:130:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingPanelVersion with data set #0";d:0.038;s:130:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingPanelVersion with data set #1";d:0.037;s:130:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingPanelVersion with data set #2";d:0.037;s:131:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingDaemonVersion with data set #0";d:0.037;s:131:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingDaemonVersion with data set #1";d:0.038;s:131:"Tests\Unit\Services\Helpers\SoftwareVersionServiceTest::testCorrectBooleanValueIsReturnedWhenCheckingDaemonVersion with data set #2";d:0.038;s:80:"Tests\Unit\Services\Locations\LocationCreationServiceTest::testLocationIsCreated";d:0.041;s:80:"Tests\Unit\Services\Locations\LocationDeletionServiceTest::testLocationIsDeleted";d:0.04;s:98:"Tests\Unit\Services\Locations\LocationDeletionServiceTest::testExceptionIsThrownIfNodesAreAttached";d:0.041;s:78:"Tests\Unit\Services\Locations\LocationUpdateServiceTest::testLocationIsUpdated";d:0.055;s:87:"Tests\Unit\Services\Locations\LocationUpdateServiceTest::testModelCanBePassedToFunction";d:0.042;s:74:"Tests\Unit\Services\Services\NestCreationServiceTest::testCreateNewService";d:0.045;s:88:"Tests\Unit\Services\Services\NestCreationServiceTest::testCreateServiceWithDefinedAuthor";d:0.042;s:74:"Tests\Unit\Services\Services\NestDeletionServiceTest::testServiceIsDeleted";d:0.041;s:112:"Tests\Unit\Services\Services\NestDeletionServiceTest::testExceptionIsThrownIfServersAreAttached with data set #0";d:0.04;s:112:"Tests\Unit\Services\Services\NestDeletionServiceTest::testExceptionIsThrownIfServersAreAttached with data set #1";d:0.039;s:112:"Tests\Unit\Services\Services\NestDeletionServiceTest::testExceptionIsThrownIfServersAreAttached with data set #2";d:0.039;s:112:"Tests\Unit\Services\Services\NestDeletionServiceTest::testExceptionIsThrownIfServersAreAttached with data set #3";d:0.039;s:87:"Tests\Unit\Services\Services\NestUpdateServiceTest::testAuthorArrayKeyIsRemovedIfPassed";d:0.041;s:95:"Tests\Unit\Services\Services\NestUpdateServiceTest::testServiceIsUpdatedWhenNoAuthorKeyIsPassed";d:0.041;s:94:"Tests\Unit\Services\Nodes\NodeCreationServiceTest::testNodeIsCreatedAndDaemonSecretIsGenerated";d:0.041;s:90:"Tests\Unit\Services\Nodes\NodeDeletionServiceTest::testNodeIsDeletedIfNoServersAreAttached";d:0.042;s:98:"Tests\Unit\Services\Nodes\NodeDeletionServiceTest::testExceptionIsThrownIfServersAreAttachedToNode";d:0.042;s:96:"Tests\Unit\Services\Nodes\NodeDeletionServiceTest::testModelCanBePassedToFunctionInPlaceOfNodeId";d:0.042;s:88:"Tests\Unit\Services\Nodes\NodeUpdateServiceTest::testNodeIsUpdatedAndDaemonSecretIsReset";d:0.061;s:93:"Tests\Unit\Services\Nodes\NodeUpdateServiceTest::testNodeIsUpdatedAndDaemonSecretIsNotChanged";d:0.043;s:81:"Tests\Unit\Services\Nodes\NodeUpdateServiceTest::testExceptionRelatedToConnection";d:0.05;s:84:"Tests\Unit\Services\Nodes\NodeUpdateServiceTest::testExceptionNotRelatedToConnection";d:0.044;s:88:"Tests\Unit\Services\Packs\ExportPackServiceTest::testFilesAreBundledIntoZipWhenRequested";d:0.06;s:87:"Tests\Unit\Services\Packs\ExportPackServiceTest::testPackConfigurationIsSavedAsJsonFile";d:0.052;s:84:"Tests\Unit\Services\Packs\ExportPackServiceTest::testPackIdCanBePassedInPlaceOfModel";d:0.047;s:97:"Tests\Unit\Services\Packs\ExportPackServiceTest::testExceptionIsThrownIfZipArchiveCannotBeCreated";d:0.045;s:94:"Tests\Unit\Services\Packs\PackCreationServiceTest::testPackIsCreatedWhenNoUploadedFileIsPassed";d:0.044;s:111:"Tests\Unit\Services\Packs\PackCreationServiceTest::testPackIsCreatedWhenUploadedFileIsProvided with data set #0";d:0.044;s:111:"Tests\Unit\Services\Packs\PackCreationServiceTest::testPackIsCreatedWhenUploadedFileIsProvided with data set #1";d:0.042;s:97:"Tests\Unit\Services\Packs\PackCreationServiceTest::testExceptionIsThrownIfInvalidUploadIsProvided";d:0.043;s:113:"Tests\Unit\Services\Packs\PackCreationServiceTest::testExceptionIsThrownIfInvalidMimetypeIsFound with data set #0";d:0.041;s:113:"Tests\Unit\Services\Packs\PackCreationServiceTest::testExceptionIsThrownIfInvalidMimetypeIsFound with data set #1";d:0.04;s:113:"Tests\Unit\Services\Packs\PackCreationServiceTest::testExceptionIsThrownIfInvalidMimetypeIsFound with data set #2";d:0.039;s:68:"Tests\Unit\Services\Packs\PackDeletionServiceTest::testPackIsDeleted";d:0.044;s:86:"Tests\Unit\Services\Packs\PackDeletionServiceTest::testPackIdCanBePassedInPlaceOfModel";d:0.042;s:96:"Tests\Unit\Services\Packs\PackDeletionServiceTest::testExceptionIsThrownIfServerIsAttachedToPack";d:0.042;s:66:"Tests\Unit\Services\Packs\PackUpdateServiceTest::testPackIsUpdated";d:0.045;s:108:"Tests\Unit\Services\Packs\PackUpdateServiceTest::testExceptionIsThrownIfModifyingEggIdWhenServersAreAttached";d:0.043;s:84:"Tests\Unit\Services\Packs\PackUpdateServiceTest::testPackIdCanBePassedInPlaceOfModel";d:0.043;s:93:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testJsonFileIsProcessed with data set #0";d:0.045;s:93:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testJsonFileIsProcessed with data set #1";d:0.042;s:75:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testZipfileIsProcessed";d:0.041;s:95:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfFileUploadIsInvalid";d:0.039;s:110:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfMimetypeIsInvalid with data set #0";d:0.039;s:110:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfMimetypeIsInvalid with data set #1";d:0.039;s:110:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfMimetypeIsInvalid with data set #2";d:0.039;s:98:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfZipArchiveIsUnreadable";d:0.041;s:121:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfZipDoesNotContainProperFiles with data set #0";d:0.041;s:121:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfZipDoesNotContainProperFiles with data set #1";d:0.052;s:121:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfZipDoesNotContainProperFiles with data set #2";d:0.039;s:107:"Tests\Unit\Services\Packs\TemplateUploadServiceTest::testExceptionIsThrownIfArchiveCannotBeExtractedFromZip";d:0.046;s:85:"Tests\Unit\Services\Schedules\ProcessScheduleServiceTest::testScheduleIsUpdatedAndRun";d:0.057;s:94:"Tests\Unit\Services\Servers\EnvironmentServiceTest::testSettingEnvironmentKeyPersistsItInArray";d:0.041;s:112:"Tests\Unit\Services\Servers\EnvironmentServiceTest::testProcessShouldReturnDefaultEnvironmentVariablesForAServer";d:0.042;s:90:"Tests\Unit\Services\Servers\EnvironmentServiceTest::testProcessShouldReturnKeySetAtRuntime";d:0.043;s:115:"Tests\Unit\Services\Servers\EnvironmentServiceTest::testProcessShouldAllowOverwritingVariablesWithConfigurationFile";d:0.041;s:99:"Tests\Unit\Services\Servers\EnvironmentServiceTest::testVariablesSetInConfigurationAllowForClosures";d:0.041;s:120:"Tests\Unit\Services\Servers\EnvironmentServiceTest::testProcessShouldAllowOverwritingDefaultVariablesWithRuntimeProvided";d:0.042;s:102:"Tests\Unit\Services\Servers\ReinstallServerServiceTest::testServerShouldBeReinstalledWhenModelIsPassed";d:0.044;s:105:"Tests\Unit\Services\Servers\ReinstallServerServiceTest::testServerShouldBeReinstalledWhenServerIdIsPassed";d:0.042;s:114:"Tests\Unit\Services\Servers\ReinstallServerServiceTest::testExceptionThrownByGuzzleShouldBeReRenderedAsDisplayable";d:0.041;s:121:"Tests\Unit\Services\Servers\ReinstallServerServiceTest::testExceptionNotThrownByGuzzleShouldNotBeTransformedToDisplayable";d:0.044;s:99:"Tests\Unit\Services\Servers\ServerConfigurationStructureServiceTest::testCorrectStructureIsReturned";d:0.057;s:116:"Tests\Unit\Services\Servers\ServerCreationServiceTest::testCreateShouldHitAllOfTheNecessaryServicesAndStoreTheServer";d:0.062;s:75:"Tests\Unit\Services\Servers\ServerCreationServiceTest::testDataIsAutoFilled";d:0.052;s:79:"Tests\Unit\Services\Servers\ServerCreationServiceTest::testAutoDeploymentObject";d:0.048;s:99:"Tests\Unit\Services\Servers\ServerCreationServiceTest::testExceptionShouldBeThrownIfTheRequestFails";d:0.046;s:81:"Tests\Unit\Services\Servers\ServerDeletionServiceTest::testForceParameterCanBeSet";d:0.046;s:89:"Tests\Unit\Services\Servers\ServerDeletionServiceTest::testServerCanBeDeletedWithoutForce";d:0.045;s:113:"Tests\Unit\Services\Servers\ServerDeletionServiceTest::testServerShouldBeDeletedEvenWhenFailureOccursIfForceIsSet";d:0.044;s:120:"Tests\Unit\Services\Servers\ServerDeletionServiceTest::testExceptionShouldBeThrownIfDaemonReturnsAnErrorAndForceIsNotSet";d:0.043;s:78:"Tests\Unit\Services\Servers\StartupCommandViewServiceTest::testServiceResponse";d:0.045;s:91:"Tests\Unit\Services\Servers\StartupModificationServiceTest::testStartupModifiedAsNormalUser";d:0.044;s:94:"Tests\Unit\Services\Servers\StartupModificationServiceTest::testStartupModificationAsAdminUser";d:0.045;s:105:"Tests\Unit\Services\Servers\SuspensionServiceTest::testFunctionShouldAcceptAnIntegerInPlaceOfAServerModel";d:0.045;s:98:"Tests\Unit\Services\Servers\SuspensionServiceTest::testServerShouldBeSuspendedWhenNoActionIsPassed";d:0.058;s:107:"Tests\Unit\Services\Servers\SuspensionServiceTest::testServerShouldBeUnsuspendedWhenUnsuspendActionIsPassed";d:0.041;s:123:"Tests\Unit\Services\Servers\SuspensionServiceTest::testNoActionShouldHappenIfServerIsAlreadyUnsuspendedAndActionIsUnsuspend";d:0.042;s:119:"Tests\Unit\Services\Servers\SuspensionServiceTest::testNoActionShouldHappenIfServerIsAlreadySuspendedAndActionIsSuspend";d:0.042;s:119:"Tests\Unit\Services\Servers\SuspensionServiceTest::testExceptionThrownByGuzzleShouldBeCaughtAndTransformedToDisplayable";d:0.043;s:96:"Tests\Unit\Services\Servers\SuspensionServiceTest::testExceptionShouldBeThrownIfActionIsNotValid";d:0.041;s:113:"Tests\Unit\Services\Servers\VariableValidatorServiceTest::testEmptyResultSetShouldBeReturnedIfNoVariablesAreFound";d:0.038;s:140:"Tests\Unit\Services\Servers\VariableValidatorServiceTest::testValidatorShouldNotProcessVariablesSetAsNotUserEditableWhenAdminFlagIsNotPassed";d:0.044;s:114:"Tests\Unit\Services\Servers\VariableValidatorServiceTest::testValidatorShouldProcessAllVariablesWhenAdminFlagIsSet";d:0.044;s:124:"Tests\Unit\Services\Servers\VariableValidatorServiceTest::testValidatorShouldThrowExceptionWhenAValidationErrorIsEncountered";d:0.042;s:95:"Tests\Unit\Services\Subusers\PermissionCreationServiceTest::testPermissionsAreAssignedCorrectly";d:0.042;s:87:"Tests\Unit\Services\Subusers\SubuserCreationServiceTest::testAccountIsCreatedForNewUser";d:0.047;s:93:"Tests\Unit\Services\Subusers\SubuserCreationServiceTest::testExistingUserCanBeAddedAsASubuser";d:0.046;s:97:"Tests\Unit\Services\Subusers\SubuserCreationServiceTest::testExceptionIsThrownIfUserIsServerOwner";d:0.042;s:101:"Tests\Unit\Services\Subusers\SubuserCreationServiceTest::testExceptionIsThrownIfUserIsAlreadyASubuser";d:0.042;s:80:"Tests\Unit\Services\Subusers\SubuserUpdateServiceTest::testPermissionsAreUpdated";d:0.043;s:99:"Tests\Unit\Services\Subusers\SubuserUpdateServiceTest::testExceptionIsThrownIfDaemonConnectionFails";d:0.043;s:83:"Tests\Unit\Services\Users\ToggleTwoFactorServiceTest::testTwoFactorIsEnabledForUser";d:0.056;s:77:"Tests\Unit\Services\Users\ToggleTwoFactorServiceTest::testTwoFactorIsDisabled";d:0.044;s:89:"Tests\Unit\Services\Users\ToggleTwoFactorServiceTest::testTwoFactorRemainsDisabledForUser";d:0.043;s:91:"Tests\Unit\Services\Users\ToggleTwoFactorServiceTest::testExceptionIsThrownIfTokenIsInvalid";d:0.045;s:82:"Tests\Unit\Services\Users\TwoFactorSetupServiceTest::testSecretAndImageAreReturned";d:0.047;s:84:"Tests\Unit\Services\UserCreationServiceTest::testUserIsCreatedWhenPasswordIsProvided";d:0.055;s:74:"Tests\Unit\Services\UserCreationServiceTest::testUuidPassedInDataIsIgnored";d:0.044;s:86:"Tests\Unit\Services\UserCreationServiceTest::testUserIsCreatedWhenNoPasswordIsProvided";d:0.043;s:99:"Tests\Unit\Services\Users\UserDeletionServiceTest::testUserIsDeletedIfNoServersAreAttachedToAccount";d:0.042;s:101:"Tests\Unit\Services\Users\UserDeletionServiceTest::testExceptionIsThrownIfServersAreAttachedToAccount";d:0.044;s:86:"Tests\Unit\Services\Users\UserDeletionServiceTest::testModelCanBePassedInPlaceOfUserId";d:0.042;s:119:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserWithoutTouchingHasherIfNoPasswordPassed with data set #0";d:0.045;s:119:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserWithoutTouchingHasherIfNoPasswordPassed with data set #1";d:0.047;s:119:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserWithoutTouchingHasherIfNoPasswordPassed with data set #2";d:0.042;s:119:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserWithoutTouchingHasherIfNoPasswordPassed with data set #3";d:0.042;s:88:"Tests\Unit\Services\Users\UserUpdateServiceTest::testUpdateUserAndHashPasswordIfProvided";d:0.042;s:90:"Tests\Unit\Services\Users\UserUpdateServiceTest::testAdministrativeUserRevokingAdminStatus";d:0.041;s:89:"Tests\Unit\Services\Users\UserUpdateServiceTest::testNormalUserShouldNotRevokeAdminStatus";d:0.041;}}} \ No newline at end of file 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. * From 83a59cdf4fd84ef2906276f1b421b25258e8c204 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Wed, 24 Jun 2020 21:54:56 -0700 Subject: [PATCH 30/63] Fix node update tests --- .../Wings/DaemonConfigurationRepository.php | 2 +- .../Helpers/SoftwareVersionService.php | 4 +- app/Services/Nodes/NodeCreationService.php | 6 +- .../Eggs/EggConfigurationServiceTest.php | 90 ------- .../Variables/VariableCreationServiceTest.php | 17 +- .../Variables/VariableUpdateServiceTest.php | 17 +- .../Helpers/SoftwareVersionServiceTest.php | 168 ------------- .../Nodes/NodeCreationServiceTest.php | 60 +++-- .../Nodes/NodeDeletionServiceTest.php | 5 +- .../Services/Nodes/NodeUpdateServiceTest.php | 221 +++++++++++++----- 10 files changed, 227 insertions(+), 363 deletions(-) delete mode 100644 tests/Unit/Services/Eggs/EggConfigurationServiceTest.php delete mode 100644 tests/Unit/Services/Helpers/SoftwareVersionServiceTest.php diff --git a/app/Repositories/Wings/DaemonConfigurationRepository.php b/app/Repositories/Wings/DaemonConfigurationRepository.php index f1b83cacb..ffd498cbc 100644 --- a/app/Repositories/Wings/DaemonConfigurationRepository.php +++ b/app/Repositories/Wings/DaemonConfigurationRepository.php @@ -34,7 +34,7 @@ class DaemonConfigurationRepository extends DaemonRepository * @return \Psr\Http\Message\ResponseInterface * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException */ - public function update(?Node $node) + public function update(Node $node) { try { return $this->getHttpClient()->post( diff --git a/app/Services/Helpers/SoftwareVersionService.php b/app/Services/Helpers/SoftwareVersionService.php index 6aa9c8935..893c097d0 100644 --- a/app/Services/Helpers/SoftwareVersionService.php +++ b/app/Services/Helpers/SoftwareVersionService.php @@ -4,7 +4,7 @@ namespace Pterodactyl\Services\Helpers; use Exception; use GuzzleHttp\Client; -use Cake\Chronos\Chronos; +use Carbon\CarbonImmutable; use Illuminate\Support\Arr; use Illuminate\Contracts\Cache\Repository as CacheRepository; use Pterodactyl\Exceptions\Service\Helper\CdnVersionFetchingException; @@ -120,7 +120,7 @@ class SoftwareVersionService */ protected function cacheVersionData() { - return $this->cache->remember(self::VERSION_CACHE_KEY, Chronos::now()->addMinutes(config()->get('pterodactyl.cdn.cache_time', 60)), function () { + return $this->cache->remember(self::VERSION_CACHE_KEY, CarbonImmutable::now()->addMinutes(config()->get('pterodactyl.cdn.cache_time', 60)), function () { try { $response = $this->client->request('GET', config()->get('pterodactyl.cdn.url')); diff --git a/app/Services/Nodes/NodeCreationService.php b/app/Services/Nodes/NodeCreationService.php index fabc36e05..a44c036bd 100644 --- a/app/Services/Nodes/NodeCreationService.php +++ b/app/Services/Nodes/NodeCreationService.php @@ -5,7 +5,7 @@ namespace Pterodactyl\Services\Nodes; use Ramsey\Uuid\Uuid; use Illuminate\Support\Str; use Pterodactyl\Models\Node; -use Illuminate\Encryption\Encrypter; +use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; class NodeCreationService @@ -16,14 +16,14 @@ class NodeCreationService protected $repository; /** - * @var \Illuminate\Encryption\Encrypter + * @var \Illuminate\Contracts\Encryption\Encrypter */ private $encrypter; /** * CreationService constructor. * - * @param \Illuminate\Encryption\Encrypter $encrypter + * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository */ public function __construct(Encrypter $encrypter, NodeRepositoryInterface $repository) 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 + ); } } From b55767426f14258601176f9b5e199dff26156f66 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Wed, 24 Jun 2020 22:18:48 -0700 Subject: [PATCH 31/63] Update some server service tests --- .../Servers/ReinstallServerService.php | 24 ++-- .../Servers/ServerCreationService.php | 3 - .../Services/Packs/ExportPackServiceTest.php | 5 +- .../Servers/ReinstallServerServiceTest.php | 121 ++++-------------- ...erverConfigurationStructureServiceTest.php | 53 ++++---- .../Servers/ServerCreationServiceTest.php | 76 ++++++----- 6 files changed, 117 insertions(+), 165 deletions(-) diff --git a/app/Services/Servers/ReinstallServerService.php b/app/Services/Servers/ReinstallServerService.php index 27955c475..a68e97110 100644 --- a/app/Services/Servers/ReinstallServerService.php +++ b/app/Services/Servers/ReinstallServerService.php @@ -4,8 +4,8 @@ namespace Pterodactyl\Services\Servers; use Pterodactyl\Models\Server; use Illuminate\Database\ConnectionInterface; +use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Repositories\Wings\DaemonServerRepository; -use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; class ReinstallServerService { @@ -17,27 +17,27 @@ class ReinstallServerService /** * @var \Illuminate\Database\ConnectionInterface */ - private $database; + private $connection; /** - * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface + * @var \Pterodactyl\Repositories\Eloquent\ServerRepository */ private $repository; /** * ReinstallService constructor. * - * @param \Illuminate\Database\ConnectionInterface $database + * @param \Illuminate\Database\ConnectionInterface $connection * @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository - * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository + * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository */ public function __construct( - ConnectionInterface $database, + ConnectionInterface $connection, DaemonServerRepository $daemonServerRepository, - ServerRepositoryInterface $repository + ServerRepository $repository ) { $this->daemonServerRepository = $daemonServerRepository; - $this->database = $database; + $this->connection = $connection; $this->repository = $repository; } @@ -51,14 +51,14 @@ class ReinstallServerService */ public function reinstall(Server $server) { - $this->database->transaction(function () use ($server) { - $this->repository->withoutFreshModel()->update($server->id, [ + return $this->connection->transaction(function () use ($server) { + $updated = $this->repository->update($server->id, [ 'installed' => Server::STATUS_INSTALLING, ]); $this->daemonServerRepository->setServer($server)->reinstall(); - }); - return $server->refresh(); + return $updated; + }); } } diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php index cce242e5a..b84bed7c5 100644 --- a/app/Services/Servers/ServerCreationService.php +++ b/app/Services/Servers/ServerCreationService.php @@ -310,8 +310,6 @@ class ServerCreationService return $allocation->node_id; } - /** @noinspection PhpDocMissingThrowsInspection */ - /** * Create a unique UUID and UUID-Short combo for a server. * @@ -319,7 +317,6 @@ class ServerCreationService */ private function generateUniqueUuidCombo(): string { - /** @noinspection PhpUnhandledExceptionInspection */ $uuid = Uuid::uuid4()->toString(); if (! $this->repository->isUniqueUuidCombo($uuid, substr($uuid, 0, 8))) { 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 From da39d9177e6c84684373aae8f5e7cef8b136ac70 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 25 Jun 2020 21:16:59 -0700 Subject: [PATCH 32/63] Fix seed imports --- app/Services/Eggs/Sharing/EggImporterService.php | 11 ++++++++++- .../Eggs/Sharing/EggUpdateImporterService.php | 11 ++++++++++- database/seeds/EggSeeder.php | 11 ++++++----- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/app/Services/Eggs/Sharing/EggImporterService.php b/app/Services/Eggs/Sharing/EggImporterService.php index 45eee14f4..006360cfa 100644 --- a/app/Services/Eggs/Sharing/EggImporterService.php +++ b/app/Services/Eggs/Sharing/EggImporterService.php @@ -76,7 +76,16 @@ class EggImporterService public function handle(UploadedFile $file, int $nest): Egg { if ($file->getError() !== UPLOAD_ERR_OK || ! $file->isFile()) { - throw new InvalidFileUploadException(trans('exceptions.nest.importer.file_error')); + throw new InvalidFileUploadException( + sprintf( + 'The selected file ["%s"] was not in a valid format to import. (is_file: %s is_valid: %s err_code: %s err: %s)', + $file->getFilename(), + $file->isFile() ? 'true' : 'false', + $file->isValid() ? 'true' : 'false', + $file->getError(), + $file->getErrorMessage() + ) + ); } $parsed = json_decode($file->openFile()->fread($file->getSize())); diff --git a/app/Services/Eggs/Sharing/EggUpdateImporterService.php b/app/Services/Eggs/Sharing/EggUpdateImporterService.php index b04904194..3acf3f90e 100644 --- a/app/Services/Eggs/Sharing/EggUpdateImporterService.php +++ b/app/Services/Eggs/Sharing/EggUpdateImporterService.php @@ -57,7 +57,16 @@ class EggUpdateImporterService public function handle(int $egg, UploadedFile $file) { if ($file->getError() !== UPLOAD_ERR_OK || ! $file->isFile()) { - throw new InvalidFileUploadException(trans('exceptions.nest.importer.file_error')); + throw new InvalidFileUploadException( + sprintf( + 'The selected file ["%s"] was not in a valid format to import. (is_file: %s is_valid: %s err_code: %s err: %s)', + $file->getFilename(), + $file->isFile() ? 'true' : 'false', + $file->isValid() ? 'true' : 'false', + $file->getError(), + $file->getErrorMessage() + ) + ); } $parsed = json_decode($file->openFile()->fread($file->getSize())); diff --git a/database/seeds/EggSeeder.php b/database/seeds/EggSeeder.php index 245d74ac3..2f77558da 100644 --- a/database/seeds/EggSeeder.php +++ b/database/seeds/EggSeeder.php @@ -112,14 +112,15 @@ class EggSeeder extends Seeder $files = $this->filesystem->allFiles(database_path('seeds/eggs/' . kebab_case($nest->name))); $this->command->alert('Updating Eggs for Nest: ' . $nest->name); - collect($files)->each(function ($file) use ($nest) { + Collection::make($files)->each(function ($file) use ($nest) { /* @var \Symfony\Component\Finder\SplFileInfo $file */ $decoded = json_decode($file->getContents()); if (json_last_error() !== JSON_ERROR_NONE) { - return $this->command->error('JSON decode exception for ' . $file->getFilename() . ': ' . json_last_error_msg()); + $this->command->error('JSON decode exception for ' . $file->getFilename() . ': ' . json_last_error_msg()); + return; } - $file = new UploadedFile($file->getPathname(), $file->getFilename(), 'application/json', $file->getSize()); + $file = new UploadedFile($file->getPathname(), $file->getFilename(), 'application/json'); try { $egg = $this->repository->setColumns('id')->findFirstWhere([ @@ -130,11 +131,11 @@ class EggSeeder extends Seeder $this->updateImporterService->handle($egg->id, $file); - return $this->command->info('Updated ' . $decoded->name); + $this->command->info('Updated ' . $decoded->name); } catch (RecordNotFoundException $exception) { $this->importerService->handle($file, $nest->id); - return $this->command->comment('Created ' . $decoded->name); + $this->command->comment('Created ' . $decoded->name); } }); From f0e3f0e4741875abd9c57a4f95e472f9de0612df Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 25 Jun 2020 21:21:05 -0700 Subject: [PATCH 33/63] Make quick iteration testing easier --- bootstrap/tests.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/bootstrap/tests.php b/bootstrap/tests.php index 276786610..c1a6c8fb0 100644 --- a/bootstrap/tests.php +++ b/bootstrap/tests.php @@ -17,12 +17,22 @@ $kernel->bootstrap(); $output = new ConsoleOutput; +if (config('database.default') !== 'testing') { + $output->writeln(PHP_EOL . 'Cannot run test process against non-testing database.'); + $output->writeln(PHP_EOL . 'Environment is currently pointed at: "' . config('database.default') . '".'); + exit(1); +} + /* * Perform database migrations and reseeding before continuing with * running the tests. */ -$output->writeln(PHP_EOL . 'Refreshing database for Integration tests...'); -$kernel->call('migrate:fresh', ['--database' => 'testing']); +if (!env('SKIP_MIGRATIONS')) { + $output->writeln(PHP_EOL . 'Refreshing database for Integration tests...'); + $kernel->call('migrate:fresh', ['--database' => 'testing']); -$output->writeln('Seeding database for Integration tests...' . PHP_EOL); -$kernel->call('db:seed', ['--database' => 'testing']); + $output->writeln('Seeding database for Integration tests...' . PHP_EOL); + $kernel->call('db:seed', ['--database' => 'testing']); +} else { + $output->writeln(PHP_EOL . 'Skipping database migrations...' . PHP_EOL); +} From 8fb21a5048b3411d6ffdaf6d5965d2213de4d1d7 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 25 Jun 2020 21:42:21 -0700 Subject: [PATCH 34/63] Fix error while updating user --- .../Api/Application/Users/UserController.php | 27 +++---------------- app/Models/User.php | 2 +- .../Http/IntegrationJsonRequestAssertions.php | 6 ++--- 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/app/Http/Controllers/Api/Application/Users/UserController.php b/app/Http/Controllers/Api/Application/Users/UserController.php index d067bab10..522cef8f4 100644 --- a/app/Http/Controllers/Api/Application/Users/UserController.php +++ b/app/Http/Controllers/Api/Application/Users/UserController.php @@ -100,39 +100,20 @@ class UserController extends ApplicationApiController * meta. If there are no errors this is an empty array. * * @param \Pterodactyl\Http\Requests\Api\Application\Users\UpdateUserRequest $request + * @param \Pterodactyl\Models\User $user * @return array * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(UpdateUserRequest $request): array + public function update(UpdateUserRequest $request, User $user): array { $this->updateService->setUserLevel(User::USER_LEVEL_ADMIN); - $collection = $this->updateService->handle($request->getModel(User::class), $request->validated()); + $user = $this->updateService->handle($user, $request->validated()); - $errors = []; - if (! empty($collection->get('exceptions'))) { - foreach ($collection->get('exceptions') as $node => $exception) { - /** @var \GuzzleHttp\Exception\RequestException $exception */ - /** @var \GuzzleHttp\Psr7\Response|null $response */ - $response = method_exists($exception, 'getResponse') ? $exception->getResponse() : null; - $message = trans('admin/server.exceptions.daemon_exception', [ - 'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(), - ]); - - $errors[] = ['message' => $message, 'node' => $node]; - } - } - - $response = $this->fractal->item($collection->get('model')) + $response = $this->fractal->item($user) ->transformWith($this->getTransformer(UserTransformer::class)); - if (count($errors) > 0) { - $response->addMeta([ - 'revocation_errors' => $errors, - ]); - } - return $response->toArray(); } diff --git a/app/Models/User.php b/app/Models/User.php index c8efc7bd6..c93fae6dd 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -164,7 +164,7 @@ class User extends Model implements 'name_last' => 'required|string|between:1,255', 'password' => 'sometimes|nullable|string', 'root_admin' => 'boolean', - 'language' => 'required|string', + 'language' => 'string', 'use_totp' => 'boolean', 'totp_secret' => 'nullable|string', ]; 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) { From 7a5f7b99a742797bc2bfc14246fbe743da2d816d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 25 Jun 2020 22:12:09 -0700 Subject: [PATCH 35/63] Add integration test covering account endpoint --- .../Api/Client/AccountController.php | 10 +- .../Api/Client/AccountControllerTest.php | 173 ++++++++++++++++++ 2 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 tests/Integration/Api/Client/AccountControllerTest.php diff --git a/app/Http/Controllers/Api/Client/AccountController.php b/app/Http/Controllers/Api/Client/AccountController.php index 5d633c480..25900f059 100644 --- a/app/Http/Controllers/Api/Client/AccountController.php +++ b/app/Http/Controllers/Api/Client/AccountController.php @@ -52,16 +52,16 @@ class AccountController extends ClientApiController * Update the authenticated user's email address. * * @param \Pterodactyl\Http\Requests\Api\Client\Account\UpdateEmailRequest $request - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function updateEmail(UpdateEmailRequest $request): Response + public function updateEmail(UpdateEmailRequest $request): JsonResponse { $this->updateService->handle($request->user(), $request->validated()); - return response('', Response::HTTP_CREATED); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } /** @@ -74,12 +74,12 @@ class AccountController extends ClientApiController * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function updatePassword(UpdatePasswordRequest $request): \Illuminate\Http\JsonResponse + public function updatePassword(UpdatePasswordRequest $request): JsonResponse { $this->updateService->handle($request->user(), $request->validated()); $this->sessionGuard->logoutOtherDevices($request->input('password')); - return JsonResponse::create([], Response::HTTP_NO_CONTENT); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } } diff --git a/tests/Integration/Api/Client/AccountControllerTest.php b/tests/Integration/Api/Client/AccountControllerTest.php new file mode 100644 index 000000000..9158ef1af --- /dev/null +++ b/tests/Integration/Api/Client/AccountControllerTest.php @@ -0,0 +1,173 @@ +forceDelete(); + + parent::tearDown(); + } + + /** + * Test that the user's account details are returned from the account endpoint. + */ + public function testAccountDetailsAreReturned() + { + /** @var \Pterodactyl\Models\User $user */ + $user = factory(User::class)->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.'); + } +} From a81f6882f7047db329fecd7a93e6a2ef01a89d00 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 25 Jun 2020 22:36:58 -0700 Subject: [PATCH 36/63] Add test coverage for API key generation and deletion --- .../Api/Client/ApiKeyController.php | 1 + .../Api/Client/ApiKeyControllerTest.php | 221 ++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 tests/Integration/Api/Client/ApiKeyControllerTest.php diff --git a/app/Http/Controllers/Api/Client/ApiKeyController.php b/app/Http/Controllers/Api/Client/ApiKeyController.php index 0788e62e2..5662e19cd 100644 --- a/app/Http/Controllers/Api/Client/ApiKeyController.php +++ b/app/Http/Controllers/Api/Client/ApiKeyController.php @@ -103,6 +103,7 @@ class ApiKeyController extends ClientApiController public function delete(ClientApiRequest $request, string $identifier) { $response = $this->repository->deleteWhere([ + 'key_type' => ApiKey::TYPE_ACCOUNT, 'user_id' => $request->user()->id, 'identifier' => $identifier, ]); diff --git a/tests/Integration/Api/Client/ApiKeyControllerTest.php b/tests/Integration/Api/Client/ApiKeyControllerTest.php new file mode 100644 index 000000000..2abf64185 --- /dev/null +++ b/tests/Integration/Api/Client/ApiKeyControllerTest.php @@ -0,0 +1,221 @@ +forceDelete(); + User::query()->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)->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]); + } +} From 4a677aebae8e6818d5a9378ee2c31f81c41e1b55 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 25 Jun 2020 22:39:45 -0700 Subject: [PATCH 37/63] Fix the subtest to actually make enough keys for this to be useful --- tests/Integration/Api/Client/ApiKeyControllerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Integration/Api/Client/ApiKeyControllerTest.php b/tests/Integration/Api/Client/ApiKeyControllerTest.php index 2abf64185..8189342b9 100644 --- a/tests/Integration/Api/Client/ApiKeyControllerTest.php +++ b/tests/Integration/Api/Client/ApiKeyControllerTest.php @@ -66,7 +66,7 @@ class ApiKeyControllerTest extends IntegrationTestCase // 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)->create([ + factory(ApiKey::class)->times(10)->create([ 'user_id' => factory(User::class)->create()->id, 'key_type' => ApiKey::TYPE_ACCOUNT, ]); From fc261fe20ccd8859c26839f07ba1b900c5bdc81e Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 27 Jun 2020 10:35:02 -0700 Subject: [PATCH 38/63] Add test cases for client servers endpoint --- app/Models/Location.php | 10 ++ database/factories/ModelFactory.php | 38 +--- .../Api/Client/ApiKeyControllerTest.php | 2 +- .../Api/Client/ClientControllerTest.php | 162 ++++++++++++++++++ tests/Integration/IntegrationTestCase.php | 3 + 5 files changed, 178 insertions(+), 37 deletions(-) create mode 100644 tests/Integration/Api/Client/ClientControllerTest.php diff --git a/app/Models/Location.php b/app/Models/Location.php index 71d3b4117..17ba7e24a 100644 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -2,6 +2,16 @@ namespace Pterodactyl\Models; +/** + * @property int $id + * @property string $short + * @property string $long + * @property \Carbon\Carbon $created_at + * @property \Carbon\Carbon $updated_at + * + * @property \Pterodactyl\Models\Node[] $nodes + * @property \Pterodactyl\Models\Server[] $servers + */ class Location extends Model { /** diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index db12c08d9..553f8a032 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -20,8 +20,6 @@ use Pterodactyl\Models\ApiKey; $factory->define(Pterodactyl\Models\Server::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'node_id' => $faker->randomNumber(), 'uuid' => $faker->unique()->uuid, 'uuidShort' => str_random(8), 'name' => $faker->firstName, @@ -34,9 +32,6 @@ $factory->define(Pterodactyl\Models\Server::class, function (Faker $faker) { 'io' => 500, 'cpu' => 0, 'oom_disabled' => 0, - 'allocation_id' => $faker->randomNumber(), - 'nest_id' => $faker->randomNumber(), - 'egg_id' => $faker->randomNumber(), 'pack_id' => null, 'installed' => 1, 'database_limit' => null, @@ -50,7 +45,6 @@ $factory->define(Pterodactyl\Models\User::class, function (Faker $faker) { static $password; return [ - 'id' => $faker->unique()->randomNumber(), 'external_id' => $faker->unique()->isbn10, 'uuid' => $faker->uuid, 'username' => $faker->userName, @@ -74,15 +68,13 @@ $factory->state(Pterodactyl\Models\User::class, 'admin', function () { $factory->define(Pterodactyl\Models\Location::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'short' => $faker->unique()->domainWord, + 'short' => Str::random(8), 'long' => $faker->catchPhrase, ]; }); $factory->define(Pterodactyl\Models\Node::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), 'uuid' => Uuid::uuid4()->toString(), 'public' => true, 'name' => $faker->firstName, @@ -104,7 +96,6 @@ $factory->define(Pterodactyl\Models\Node::class, function (Faker $faker) { $factory->define(Pterodactyl\Models\Nest::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), 'uuid' => $faker->unique()->uuid, 'author' => 'testauthor@example.com', 'name' => $faker->word, @@ -114,9 +105,7 @@ $factory->define(Pterodactyl\Models\Nest::class, function (Faker $faker) { $factory->define(Pterodactyl\Models\Egg::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), 'uuid' => $faker->unique()->uuid, - 'nest_id' => $faker->unique()->randomNumber(), 'name' => $faker->name, 'description' => implode(' ', $faker->sentences(3)), 'startup' => 'java -jar test.jar', @@ -125,7 +114,6 @@ $factory->define(Pterodactyl\Models\Egg::class, function (Faker $faker) { $factory->define(Pterodactyl\Models\EggVariable::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), 'name' => $faker->firstName, 'description' => $faker->sentence(), 'env_variable' => strtoupper(str_replace(' ', '_', $faker->words(2, true))), @@ -146,8 +134,6 @@ $factory->state(Pterodactyl\Models\EggVariable::class, 'editable', function () { $factory->define(Pterodactyl\Models\Pack::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'egg_id' => $faker->randomNumber(), 'uuid' => $faker->uuid, 'name' => $faker->word, 'description' => null, @@ -159,17 +145,11 @@ $factory->define(Pterodactyl\Models\Pack::class, function (Faker $faker) { }); $factory->define(Pterodactyl\Models\Subuser::class, function (Faker $faker) { - return [ - 'id' => $faker->unique()->randomNumber(), - 'user_id' => $faker->randomNumber(), - 'server_id' => $faker->randomNumber(), - ]; + return []; }); $factory->define(Pterodactyl\Models\Allocation::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'node_id' => $faker->randomNumber(), 'ip' => $faker->ipv4, 'port' => $faker->randomNumber(5), ]; @@ -177,13 +157,11 @@ $factory->define(Pterodactyl\Models\Allocation::class, function (Faker $faker) { $factory->define(Pterodactyl\Models\DatabaseHost::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), 'name' => $faker->colorName, 'host' => $faker->unique()->ipv4, 'port' => 3306, 'username' => $faker->colorName, 'password' => Crypt::encrypt($faker->word), - 'node_id' => $faker->randomNumber(), ]; }); @@ -191,9 +169,6 @@ $factory->define(Pterodactyl\Models\Database::class, function (Faker $faker) { static $password; return [ - 'id' => $faker->unique()->randomNumber(), - 'server_id' => $faker->randomNumber(), - 'database_host_id' => $faker->randomNumber(), 'database' => str_random(10), 'username' => str_random(10), 'remote' => '%', @@ -205,16 +180,12 @@ $factory->define(Pterodactyl\Models\Database::class, function (Faker $faker) { $factory->define(Pterodactyl\Models\Schedule::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'server_id' => $faker->randomNumber(), 'name' => $faker->firstName(), ]; }); $factory->define(Pterodactyl\Models\Task::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'schedule_id' => $faker->randomNumber(), 'sequence_id' => $faker->randomNumber(1), 'action' => 'command', 'payload' => 'test command', @@ -225,9 +196,6 @@ $factory->define(Pterodactyl\Models\Task::class, function (Faker $faker) { $factory->define(Pterodactyl\Models\DaemonKey::class, function (Faker $faker) { return [ - 'id' => $faker->unique()->randomNumber(), - 'server_id' => $faker->randomNumber(), - 'user_id' => $faker->randomNumber(), 'secret' => 'i_' . str_random(40), 'expires_at' => \Carbon\Carbon::now()->addMinutes(10)->toDateTimeString(), ]; @@ -237,8 +205,6 @@ $factory->define(Pterodactyl\Models\ApiKey::class, function (Faker $faker) { static $token; return [ - 'id' => $faker->unique()->randomNumber(), - 'user_id' => $faker->randomNumber(), 'key_type' => ApiKey::TYPE_APPLICATION, 'identifier' => str_random(Pterodactyl\Models\ApiKey::IDENTIFIER_LENGTH), 'token' => $token ?: $token = encrypt(str_random(Pterodactyl\Models\ApiKey::KEY_LENGTH)), diff --git a/tests/Integration/Api/Client/ApiKeyControllerTest.php b/tests/Integration/Api/Client/ApiKeyControllerTest.php index 8189342b9..f0b35a519 100644 --- a/tests/Integration/Api/Client/ApiKeyControllerTest.php +++ b/tests/Integration/Api/Client/ApiKeyControllerTest.php @@ -63,7 +63,7 @@ class ApiKeyControllerTest extends IntegrationTestCase /** @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 + // 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([ diff --git a/tests/Integration/Api/Client/ClientControllerTest.php b/tests/Integration/Api/Client/ClientControllerTest.php new file mode 100644 index 000000000..eb88d122c --- /dev/null +++ b/tests/Integration/Api/Client/ClientControllerTest.php @@ -0,0 +1,162 @@ +forceDelete(); + User::query()->forceDelete(); + Node::query()->forceDelete(); + Location::query()->forceDelete(); + + parent::tearDown(); + } + + /** + * Test that only the servers a logged in user is assigned to are returned by the + * API endpoint. Obviously there are cases such as being an administrator or being + * a subuser, but for this test we just want to test a basic scenario and pretend + * subusers do not exist at all. + */ + public function testOnlyLoggedInUsersServersAreReturned() + { + /** @var \Pterodactyl\Models\User[] $users */ + $users = factory(User::class)->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/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. */ From 4cb95d80639045b55fd79104f6286fbb1fb44a88 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 27 Jun 2020 11:06:35 -0700 Subject: [PATCH 39/63] Add test coverage for 2fa --- .../Api/Client/TwoFactorController.php | 8 +- .../Api/Client/TwoFactorControllerTest.php | 158 ++++++++++++++++++ 2 files changed, 162 insertions(+), 4 deletions(-) create mode 100644 tests/Integration/Api/Client/TwoFactorControllerTest.php diff --git a/app/Http/Controllers/Api/Client/TwoFactorController.php b/app/Http/Controllers/Api/Client/TwoFactorController.php index 0dae225e5..8c8acfdf4 100644 --- a/app/Http/Controllers/Api/Client/TwoFactorController.php +++ b/app/Http/Controllers/Api/Client/TwoFactorController.php @@ -61,11 +61,11 @@ class TwoFactorController extends ClientApiController */ public function index(Request $request) { - if ($request->user()->totp_enabled) { + if ($request->user()->use_totp) { throw new BadRequestHttpException('Two-factor authentication is already enabled on this account.'); } - return JsonResponse::create([ + return new JsonResponse([ 'data' => [ 'image_url_data' => $this->setupService->handle($request->user()), ], @@ -98,7 +98,7 @@ class TwoFactorController extends ClientApiController $this->toggleTwoFactorService->handle($request->user(), $request->input('code'), true); - return JsonResponse::create([], Response::HTTP_NO_CONTENT); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } /** @@ -124,6 +124,6 @@ class TwoFactorController extends ClientApiController 'use_totp' => false, ]); - return JsonResponse::create([], Response::HTTP_NO_CONTENT); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } } diff --git a/tests/Integration/Api/Client/TwoFactorControllerTest.php b/tests/Integration/Api/Client/TwoFactorControllerTest.php new file mode 100644 index 000000000..b49c1b57c --- /dev/null +++ b/tests/Integration/Api/Client/TwoFactorControllerTest.php @@ -0,0 +1,158 @@ +forceDelete(); + + parent::tearDown(); + } + + /** + * Test that image data for enabling 2FA is returned by the endpoint and that the user + * record in the database is updated as expected. + */ + public function testTwoFactorImageDataIsReturned() + { + /** @var \Pterodactyl\Models\User $user */ + $user = factory(User::class)->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); + } +} From 8cfdb3acce5a35eeae0098c515b9202145d9f338 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 27 Jun 2020 12:04:41 -0700 Subject: [PATCH 40/63] Add test cases for sending a command to a server --- .../Api/Client/Servers/SendCommandRequest.php | 3 +- database/factories/ModelFactory.php | 2 +- .../Api/Client/AccountControllerTest.php | 13 +-- .../Api/Client/ApiKeyControllerTest.php | 4 +- .../Client/ClientApiIntegrationTestCase.php | 53 ++++++++++ .../Api/Client/ClientControllerTest.php | 18 +--- .../Client/Server/CommandControllerTest.php | 100 ++++++++++++++++++ .../Api/Client/TwoFactorControllerTest.php | 13 +-- 8 files changed, 160 insertions(+), 46 deletions(-) create mode 100644 tests/Integration/Api/Client/ClientApiIntegrationTestCase.php create mode 100644 tests/Integration/Api/Client/Server/CommandControllerTest.php diff --git a/app/Http/Requests/Api/Client/Servers/SendCommandRequest.php b/app/Http/Requests/Api/Client/Servers/SendCommandRequest.php index fe0dd1757..3f2f6c196 100644 --- a/app/Http/Requests/Api/Client/Servers/SendCommandRequest.php +++ b/app/Http/Requests/Api/Client/Servers/SendCommandRequest.php @@ -3,8 +3,9 @@ namespace Pterodactyl\Http\Requests\Api\Client\Servers; use Pterodactyl\Models\Permission; +use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; -class SendCommandRequest extends GetServerRequest +class SendCommandRequest extends ClientApiRequest { /** * Determine if the API user has permission to perform this action. diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 553f8a032..6d6208f4f 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -20,7 +20,7 @@ use Pterodactyl\Models\ApiKey; $factory->define(Pterodactyl\Models\Server::class, function (Faker $faker) { return [ - 'uuid' => $faker->unique()->uuid, + 'uuid' => Uuid::uuid4()->toString(), 'uuidShort' => str_random(8), 'name' => $faker->firstName, 'description' => implode(' ', $faker->sentences()), diff --git a/tests/Integration/Api/Client/AccountControllerTest.php b/tests/Integration/Api/Client/AccountControllerTest.php index 9158ef1af..4fbef8749 100644 --- a/tests/Integration/Api/Client/AccountControllerTest.php +++ b/tests/Integration/Api/Client/AccountControllerTest.php @@ -6,20 +6,9 @@ use Mockery; use Pterodactyl\Models\User; use Illuminate\Http\Response; use Illuminate\Auth\AuthManager; -use Pterodactyl\Tests\Integration\IntegrationTestCase; -class AccountControllerTest extends IntegrationTestCase +class AccountControllerTest extends ClientApiIntegrationTestCase { - /** - * Clean up after running tests. - */ - protected function tearDown(): void - { - User::query()->forceDelete(); - - parent::tearDown(); - } - /** * Test that the user's account details are returned from the account endpoint. */ diff --git a/tests/Integration/Api/Client/ApiKeyControllerTest.php b/tests/Integration/Api/Client/ApiKeyControllerTest.php index f0b35a519..13a0b9c84 100644 --- a/tests/Integration/Api/Client/ApiKeyControllerTest.php +++ b/tests/Integration/Api/Client/ApiKeyControllerTest.php @@ -5,9 +5,8 @@ namespace Pterodactyl\Tests\Integration\Api\Client; use Pterodactyl\Models\User; use Illuminate\Http\Response; use Pterodactyl\Models\ApiKey; -use Pterodactyl\Tests\Integration\IntegrationTestCase; -class ApiKeyControllerTest extends IntegrationTestCase +class ApiKeyControllerTest extends ClientApiIntegrationTestCase { /** * Cleanup after tests. @@ -15,7 +14,6 @@ class ApiKeyControllerTest extends IntegrationTestCase protected function tearDown(): void { ApiKey::query()->forceDelete(); - User::query()->forceDelete(); parent::tearDown(); } diff --git a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php new file mode 100644 index 000000000..ed36e3dd4 --- /dev/null +++ b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php @@ -0,0 +1,53 @@ +forceDelete(); + Node::query()->forceDelete(); + Location::query()->forceDelete(); + User::query()->forceDelete(); + + parent::tearDown(); + } + + /** + * 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]; + } +} diff --git a/tests/Integration/Api/Client/ClientControllerTest.php b/tests/Integration/Api/Client/ClientControllerTest.php index eb88d122c..1561f59cf 100644 --- a/tests/Integration/Api/Client/ClientControllerTest.php +++ b/tests/Integration/Api/Client/ClientControllerTest.php @@ -2,29 +2,13 @@ namespace Pterodactyl\Tests\Integration\Api\Client; -use Pterodactyl\Models\Node; use Pterodactyl\Models\User; use Pterodactyl\Models\Server; use Pterodactyl\Models\Subuser; -use Pterodactyl\Models\Location; use Pterodactyl\Models\Permission; -use Pterodactyl\Tests\Integration\IntegrationTestCase; -class ClientControllerTest extends IntegrationTestCase +class ClientControllerTest extends ClientApiIntegrationTestCase { - /** - * Cleanup after tests are run. - */ - protected function tearDown(): void - { - Server::query()->forceDelete(); - User::query()->forceDelete(); - Node::query()->forceDelete(); - Location::query()->forceDelete(); - - parent::tearDown(); - } - /** * Test that only the servers a logged in user is assigned to are returned by the * API endpoint. Obviously there are cases such as being an administrator or being 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/TwoFactorControllerTest.php b/tests/Integration/Api/Client/TwoFactorControllerTest.php index b49c1b57c..8344d2b96 100644 --- a/tests/Integration/Api/Client/TwoFactorControllerTest.php +++ b/tests/Integration/Api/Client/TwoFactorControllerTest.php @@ -6,20 +6,9 @@ use Carbon\Carbon; use Pterodactyl\Models\User; use Illuminate\Http\Response; use PragmaRX\Google2FA\Google2FA; -use Pterodactyl\Tests\Integration\IntegrationTestCase; -class TwoFactorControllerTest extends IntegrationTestCase +class TwoFactorControllerTest extends ClientApiIntegrationTestCase { - /** - * Clean up after tests have run. - */ - protected function tearDown(): void - { - User::query()->forceDelete(); - - parent::tearDown(); - } - /** * Test that image data for enabling 2FA is returned by the endpoint and that the user * record in the database is updated as expected. From 6312b627a38da0f05c22719923c85635c6e2324d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 27 Jun 2020 12:23:40 -0700 Subject: [PATCH 41/63] Add test coverage for power toggling --- .../Api/Client/Server/PowerControllerTest.php | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 tests/Integration/Api/Client/Server/PowerControllerTest.php 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], + ]; + } +} From 7546d54b4e29377da31344302ce948b5b0e2ee19 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 27 Jun 2020 12:32:27 -0700 Subject: [PATCH 42/63] Add test coverage for resource utilization --- .../ResourceUtilitizationControllerTest.php | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/Integration/Api/Client/Server/ResourceUtilitizationControllerTest.php 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, + ], + ], + ]); + } +} From 4cb4dfecc88191f474d1f20e4408fc23d6fbb721 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 28 Jun 2020 10:16:15 -0700 Subject: [PATCH 43/63] Add test coverage for generating JWTs to connect to websocket --- .../Client/Servers/WebsocketController.php | 13 +-- .../Client/Server/WebsocketControllerTest.php | 98 +++++++++++++++++++ 2 files changed, 100 insertions(+), 11 deletions(-) create mode 100644 tests/Integration/Api/Client/Server/WebsocketControllerTest.php diff --git a/app/Http/Controllers/Api/Client/Servers/WebsocketController.php b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php index 7cbe31631..a176f66fc 100644 --- a/app/Http/Controllers/Api/Client/Servers/WebsocketController.php +++ b/app/Http/Controllers/Api/Client/Servers/WebsocketController.php @@ -7,7 +7,6 @@ use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; use Pterodactyl\Models\Permission; -use Illuminate\Contracts\Cache\Repository; use Pterodactyl\Services\Nodes\NodeJWTService; use Symfony\Component\HttpKernel\Exception\HttpException; use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest; @@ -16,11 +15,6 @@ use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; class WebsocketController extends ClientApiController { - /** - * @var \Illuminate\Contracts\Cache\Repository - */ - private $cache; - /** * @var \Pterodactyl\Services\Nodes\NodeJWTService */ @@ -36,16 +30,13 @@ class WebsocketController extends ClientApiController * * @param \Pterodactyl\Services\Nodes\NodeJWTService $jwtService * @param \Pterodactyl\Services\Servers\GetUserPermissionsService $permissionsService - * @param \Illuminate\Contracts\Cache\Repository $cache */ public function __construct( NodeJWTService $jwtService, - GetUserPermissionsService $permissionsService, - Repository $cache + GetUserPermissionsService $permissionsService ) { parent::__construct(); - $this->cache = $cache; $this->jwtService = $jwtService; $this->permissionsService = $permissionsService; } @@ -78,7 +69,7 @@ class WebsocketController extends ClientApiController $socket = str_replace(['https://', 'http://'], ['wss://', 'ws://'], $server->node->getConnectionAddress()); - return JsonResponse::create([ + return new JsonResponse([ 'data' => [ 'token' => $token->__toString(), 'socket' => $socket . sprintf('/api/servers/%s/ws', $server->uuid), 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')); + } +} From 63bc4080d58be792856a371eb39e0aae013d57c8 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 28 Jun 2020 10:40:41 -0700 Subject: [PATCH 44/63] Add test coverage for reinstall & server renaming --- .../Api/Client/Servers/SettingsController.php | 4 +- .../Servers/ReinstallServerService.php | 2 +- .../Client/Server/SettingsControllerTest.php | 128 ++++++++++++++++++ 3 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 tests/Integration/Api/Client/Server/SettingsControllerTest.php diff --git a/app/Http/Controllers/Api/Client/Servers/SettingsController.php b/app/Http/Controllers/Api/Client/Servers/SettingsController.php index 64c73a8de..6090906ee 100644 --- a/app/Http/Controllers/Api/Client/Servers/SettingsController.php +++ b/app/Http/Controllers/Api/Client/Servers/SettingsController.php @@ -55,7 +55,7 @@ class SettingsController extends ClientApiController 'name' => $request->input('name'), ]); - return JsonResponse::create([], Response::HTTP_NO_CONTENT); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } /** @@ -71,6 +71,6 @@ class SettingsController extends ClientApiController { $this->reinstallServerService->reinstall($server); - return JsonResponse::create([], Response::HTTP_ACCEPTED); + return new JsonResponse([], Response::HTTP_ACCEPTED); } } diff --git a/app/Services/Servers/ReinstallServerService.php b/app/Services/Servers/ReinstallServerService.php index a68e97110..aff0321ce 100644 --- a/app/Services/Servers/ReinstallServerService.php +++ b/app/Services/Servers/ReinstallServerService.php @@ -54,7 +54,7 @@ class ReinstallServerService return $this->connection->transaction(function () use ($server) { $updated = $this->repository->update($server->id, [ 'installed' => Server::STATUS_INSTALLING, - ]); + ], true, true); $this->daemonServerRepository->setServer($server)->reinstall(); 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]]]; + } +} From b9a451b528cbe1f9aebda6fcfc2d596ecded582b Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 28 Jun 2020 13:50:07 -0700 Subject: [PATCH 45/63] Add test coverage for schedules --- .../Api/Client/Servers/ScheduleController.php | 2 +- .../Schedules/StoreScheduleRequest.php | 2 +- .../Client/ClientApiIntegrationTestCase.php | 38 +++++++ .../Schedule/CreateServerScheduleTest.php | 96 ++++++++++++++++ .../Schedule/DeleteServerScheduleTest.php | 88 +++++++++++++++ .../Schedule/GetServerSchedulesTest.php | 106 ++++++++++++++++++ .../Schedule/UpdateServerScheduleTest.php | 84 ++++++++++++++ 7 files changed, 414 insertions(+), 2 deletions(-) create mode 100644 tests/Integration/Api/Client/Server/Schedule/CreateServerScheduleTest.php create mode 100644 tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php create mode 100644 tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php create mode 100644 tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php index d1a6b9f4f..593bbc259 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php @@ -147,7 +147,7 @@ class ScheduleController extends ClientApiController { $this->repository->delete($schedule->id); - return JsonResponse::create([], Response::HTTP_NO_CONTENT); + return new JsonResponse([], Response::HTTP_NO_CONTENT); } /** diff --git a/app/Http/Requests/Api/Client/Servers/Schedules/StoreScheduleRequest.php b/app/Http/Requests/Api/Client/Servers/Schedules/StoreScheduleRequest.php index 3db1aca6d..618cd9f4e 100644 --- a/app/Http/Requests/Api/Client/Servers/Schedules/StoreScheduleRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Schedules/StoreScheduleRequest.php @@ -21,7 +21,7 @@ class StoreScheduleRequest extends ViewScheduleRequest { return [ 'name' => 'required|string|min:1', - 'is_active' => 'boolean', + 'is_active' => 'filled|boolean', 'minute' => 'required|string', 'hour' => 'required|string', 'day_of_month' => 'required|string', diff --git a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php index ed36e3dd4..8fdb969fa 100644 --- a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php +++ b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php @@ -2,12 +2,18 @@ namespace Pterodactyl\Tests\Integration\Api\Client; +use Carbon\Carbon; +use ReflectionClass; +use Carbon\CarbonImmutable; use Pterodactyl\Models\Node; use Pterodactyl\Models\User; +use Pterodactyl\Models\Model; use Pterodactyl\Models\Server; use Pterodactyl\Models\Subuser; use Pterodactyl\Models\Location; +use Illuminate\Support\Collection; use Pterodactyl\Tests\Integration\IntegrationTestCase; +use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; abstract class ClientApiIntegrationTestCase extends IntegrationTestCase { @@ -24,6 +30,17 @@ abstract class ClientApiIntegrationTestCase extends IntegrationTestCase 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()); + } + /** * 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. @@ -50,4 +67,25 @@ abstract class ClientApiIntegrationTestCase extends IntegrationTestCase 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/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..d5f002b49 --- /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(); + + $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..d7afa0d09 --- /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(); + + $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..d5c5cf235 --- /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(); + + $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]]]; + } +} From 28c5729e4865dcaf7feed79c0ce99094c9a63d72 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 28 Jun 2020 14:41:22 -0700 Subject: [PATCH 46/63] Add test coverage for creating tasks --- .../Service/ServiceLimitExceededException.php | 21 +++ .../Client/Servers/ScheduleTaskController.php | 9 +- config/pterodactyl.php | 5 + .../Client/ClientApiIntegrationTestCase.php | 30 +++ .../Schedule/DeleteServerScheduleTest.php | 2 +- .../Schedule/GetServerSchedulesTest.php | 2 +- .../Schedule/UpdateServerScheduleTest.php | 2 +- .../CreateServerScheduleTaskTest.php | 177 ++++++++++++++++++ 8 files changed, 243 insertions(+), 5 deletions(-) create mode 100644 app/Exceptions/Service/ServiceLimitExceededException.php create mode 100644 tests/Integration/Api/Client/Server/ScheduleTask/CreateServerScheduleTaskTest.php diff --git a/app/Exceptions/Service/ServiceLimitExceededException.php b/app/Exceptions/Service/ServiceLimitExceededException.php new file mode 100644 index 000000000..55ee6c94b --- /dev/null +++ b/app/Exceptions/Service/ServiceLimitExceededException.php @@ -0,0 +1,21 @@ +server_id !== $server->id) { - throw new NotFoundHttpException; + $limit = config('pterodactyl.client_features.schedules.per_schedule_task_limit', 10); + if ($schedule->tasks()->count() >= $limit) { + throw new ServiceLimitExceededException( + "Schedules may not have more than {$limit} tasks associated with them. Creating this task would put this schedule over the limit." + ); } $lastTask = $schedule->tasks->last(); diff --git a/config/pterodactyl.php b/config/pterodactyl.php index fe4f2372b..92404cd98 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -162,6 +162,11 @@ return [ 'enabled' => env('PTERODACTYL_CLIENT_DATABASES_ENABLED', true), 'allow_random' => env('PTERODACTYL_CLIENT_DATABASES_ALLOW_RANDOM', true), ], + + 'schedules' => [ + // The total number of tasks that can exist for any given schedule at once. + 'per_schedule_task_limit' => 10, + ], ], /* diff --git a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php index 8fdb969fa..e6dbf0974 100644 --- a/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php +++ b/tests/Integration/Api/Client/ClientApiIntegrationTestCase.php @@ -6,11 +6,14 @@ use Carbon\Carbon; use ReflectionClass; use Carbon\CarbonImmutable; use Pterodactyl\Models\Node; +use Pterodactyl\Models\Task; use Pterodactyl\Models\User; +use Webmozart\Assert\Assert; use Pterodactyl\Models\Model; use Pterodactyl\Models\Server; use Pterodactyl\Models\Subuser; use Pterodactyl\Models\Location; +use Pterodactyl\Models\Schedule; use Illuminate\Support\Collection; use Pterodactyl\Tests\Integration\IntegrationTestCase; use Pterodactyl\Transformers\Api\Client\BaseClientTransformer; @@ -41,6 +44,33 @@ abstract class ClientApiIntegrationTestCase extends IntegrationTestCase 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. diff --git a/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php b/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php index d5f002b49..cff7591ba 100644 --- a/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/DeleteServerScheduleTest.php @@ -50,7 +50,7 @@ class DeleteServerScheduleTest extends ClientApiIntegrationTestCase public function testNotFoundErrorIsReturnedIfScheduleDoesNotBelongToServer() { [$user, $server] = $this->generateTestAccount(); - [, $server2] = $this->generateTestAccount(); + [, $server2] = $this->generateTestAccount(['user_id' => $user->id]); $schedule = factory(Schedule::class)->create(['server_id' => $server2->id]); diff --git a/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php b/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php index d7afa0d09..67be3fd84 100644 --- a/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/GetServerSchedulesTest.php @@ -64,7 +64,7 @@ class GetServerSchedulesTest extends ClientApiIntegrationTestCase public function testScheduleBelongingToAnotherServerCannotBeViewed() { [$user, $server] = $this->generateTestAccount(); - [, $server2] = $this->generateTestAccount(); + [, $server2] = $this->generateTestAccount(['user_id' => $user->id]); $schedule = factory(Schedule::class)->create(['server_id' => $server2->id]); diff --git a/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php b/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php index d5c5cf235..d2d3132ff 100644 --- a/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php +++ b/tests/Integration/Api/Client/Server/Schedule/UpdateServerScheduleTest.php @@ -50,7 +50,7 @@ class UpdateServerScheduleTest extends ClientApiIntegrationTestCase public function testErrorIsReturnedIfScheduleDoesNotBelongToServer() { [$user, $server] = $this->generateTestAccount(); - [, $server2] = $this->generateTestAccount(); + [, $server2] = $this->generateTestAccount(['user_id' => $user->id]); $schedule = factory(Schedule::class)->create(['server_id' => $server2->id]); 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]]]; + } +} From d7ab0a356f9744d062925fcf3afd9bb11625af6d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 28 Jun 2020 15:04:54 -0700 Subject: [PATCH 47/63] Add funding information --- .github/FUNDING.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..985087d16 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: [DaneEveritt] +custom: ["https://paypal.me/PterodactylSoftware"] From b7f5011abd12eea4e45505b2c9e0b425f72015ea Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 28 Jun 2020 15:09:17 -0700 Subject: [PATCH 48/63] Update FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 985087d16..785de6c73 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ -github: [DaneEveritt] +#github: [DaneEveritt] custom: ["https://paypal.me/PterodactylSoftware"] From faca237049b7ec41d3b9f921c26c14ae2c05c558 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 28 Jun 2020 15:17:36 -0700 Subject: [PATCH 49/63] First pass at getting integration tests running on Github actions --- .editorconfig | 3 +++ .github/workflows/tests.yml | 38 +++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.editorconfig b/.editorconfig index bc49d523e..1ba6cf9f7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,5 +8,8 @@ indent_size = 4 charset = utf-8 trim_trailing_whitespace = true +[.*yml] +indent_size = 2 + [*.md] trim_trailing_whitespace = false diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..fe8c2b33d --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,38 @@ +name: tests +on: + push: + pull_request: +jobs: + integration_tests: + runs-on: ubuntu-latest + services: + mysql: + image: mysql:5.7 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: panel_test + ports: + - 3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + strategy: + fail-fast: true + matrix: + php: [7.3, 7.4] + name: PHP ${{ matrix.php }} Integration Tests + steps: + - name: checkout + uses: actions/checkout@v2 + - name: setup + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: cli, openssl, gd, mysql, pdo, mbstring, tokenizer, bcmath, xml, curl, zip + tools: composer:v1 + coverage: none + - name: install dependencies + run: composer install --prefer-dist --no-interation --no-progress + - name: execute tests + run: vendor/bin/phpunit tests/Integration + env: + TESTING_DB_PORT: ${{ job.services.mysql.ports[3306] }} + TESTING_DB_USERNAME: root From ce138d07a951fcb8e2840ac552695eb3325ae3a6 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 28 Jun 2020 15:22:39 -0700 Subject: [PATCH 50/63] Test fixes --- .github/workflows/tests.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fe8c2b33d..850563836 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,10 +18,21 @@ jobs: fail-fast: true matrix: php: [7.3, 7.4] - name: PHP ${{ matrix.php }} Integration Tests + name: Integration (PHP ${{ matrix.php }}) steps: - name: checkout uses: actions/checkout@v2 + - name: get cache directory + id: composer-cache + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + - name: cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**.composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer-${{ matrix.php }}- - name: setup uses: shivammathur/setup-php@v2 with: @@ -30,7 +41,7 @@ jobs: tools: composer:v1 coverage: none - name: install dependencies - run: composer install --prefer-dist --no-interation --no-progress + run: composer install --prefer-dist --no-interaction --no-progress - name: execute tests run: vendor/bin/phpunit tests/Integration env: From 6b1b478cb9a8eb2f47d7d12b2d38eb9f13600e14 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 28 Jun 2020 15:26:09 -0700 Subject: [PATCH 51/63] Fix envrionment configuration --- .env.travis => .env.ci | 6 +++--- .github/workflows/tests.yml | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) rename .env.travis => .env.ci (77%) diff --git a/.env.travis b/.env.ci similarity index 77% rename from .env.travis rename to .env.ci index e0040b948..e99f46691 100644 --- a/.env.travis +++ b/.env.ci @@ -2,13 +2,13 @@ APP_ENV=testing APP_DEBUG=true APP_KEY=SomeRandomString3232RandomString APP_THEME=pterodactyl -APP_TIMEZONE=UTC +APP_TIMEZONE=America/Los_Angeles APP_URL=http://localhost/ TESTING_DB_HOST=127.0.0.1 -TESTING_DB_DATABASE=travis +TESTING_DB_DATABASE=panel_test TESTING_DB_USERNAME=root -TESTING_DB_PASSWORD="" +TESTING_DB_PASSWORD= CACHE_DRIVER=array SESSION_DRIVER=array diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 850563836..5d99550d6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,6 +40,8 @@ jobs: extensions: cli, openssl, gd, mysql, pdo, mbstring, tokenizer, bcmath, xml, curl, zip tools: composer:v1 coverage: none + - name: configure + run: cp .env.ci .env - name: install dependencies run: composer install --prefer-dist --no-interaction --no-progress - name: execute tests From d6b765ed16ae7dccc9670da680016817935029de Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 28 Jun 2020 15:31:06 -0700 Subject: [PATCH 52/63] Run unit tests as well as integration tests --- .github/workflows/tests.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5d99550d6..4b22f0219 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: fail-fast: true matrix: php: [7.3, 7.4] - name: Integration (PHP ${{ matrix.php }}) + name: PHP ${{ matrix.php }} steps: - name: checkout uses: actions/checkout@v2 @@ -44,7 +44,12 @@ jobs: run: cp .env.ci .env - name: install dependencies run: composer install --prefer-dist --no-interaction --no-progress - - name: execute tests + - name: execute unit tests + run: vendor/bin/phpunit --bootstrap bootstrap/app.php tests/Unit + env: + DB_CONNECTION: testing + TESTING_DB_HOST: UNIT_NO_DB + - name: execute integration tests run: vendor/bin/phpunit tests/Integration env: TESTING_DB_PORT: ${{ job.services.mysql.ports[3306] }} From d0a4d477eb8d27335201445d77c6dac9a03e2d14 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 28 Jun 2020 15:40:02 -0700 Subject: [PATCH 53/63] Testing improvements; always run the integration & unit tests even if failure occurred --- .github/workflows/tests.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4b22f0219..a507466e1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,10 +29,12 @@ jobs: - name: cache dependencies uses: actions/cache@v2 with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**.composer.lock') }} + path: | + ~/.php_cs.cache + ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-cache-${{ matrix.php }}-${{ hashFiles('**.composer.lock') }} restore-keys: | - ${{ runner.os }}-composer-${{ matrix.php }}- + ${{ runner.os }}-cache-${{ matrix.php }}- - name: setup uses: shivammathur/setup-php@v2 with: @@ -44,13 +46,17 @@ jobs: run: cp .env.ci .env - name: install dependencies run: composer install --prefer-dist --no-interaction --no-progress + - name: run cs-fixer + run: vendor/bin/php-cs-fixer fix --dry-run --diff --diff-format=udiff - name: execute unit tests run: vendor/bin/phpunit --bootstrap bootstrap/app.php tests/Unit + if: ${{ always() }} env: DB_CONNECTION: testing TESTING_DB_HOST: UNIT_NO_DB - name: execute integration tests run: vendor/bin/phpunit tests/Integration + if: ${{ always() }} env: TESTING_DB_PORT: ${{ job.services.mysql.ports[3306] }} TESTING_DB_USERNAME: root From 6143b656a34176d9643c11790d80936a74ce3bff Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 28 Jun 2020 15:41:46 -0700 Subject: [PATCH 54/63] Don't make tests fail just because of a style issue --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a507466e1..a28a2d7ee 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,6 +48,7 @@ jobs: run: composer install --prefer-dist --no-interaction --no-progress - name: run cs-fixer run: vendor/bin/php-cs-fixer fix --dry-run --diff --diff-format=udiff + continue-on-error: true - name: execute unit tests run: vendor/bin/phpunit --bootstrap bootstrap/app.php tests/Unit if: ${{ always() }} From 2ed3763d21f2b47f2d88a72a92129edb27b09f53 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 28 Jun 2020 15:43:44 -0700 Subject: [PATCH 55/63] cs fix --- .github/workflows/tests.yml | 1 + .../Commands/Migration/CleanOrphanedApiKeysCommand.php | 2 +- app/Contracts/Repository/SessionRepositoryInterface.php | 2 +- app/Contracts/Repository/TaskRepositoryInterface.php | 2 +- app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php | 2 +- app/Repositories/Concerns/Searchable.php | 2 +- app/Repositories/Eloquent/DatabaseRepository.php | 2 +- app/Repositories/Eloquent/SessionRepository.php | 2 +- app/Repositories/Eloquent/TaskRepository.php | 2 +- app/Services/Helpers/AssetHashService.php | 2 +- bootstrap/tests.php | 2 +- config/logging.php | 2 -- .../2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php | 1 - .../2020_04_17_203438_allow_nullable_descriptions.php | 4 ++-- .../2020_04_22_055500_add_max_connections_column.php | 4 ++-- .../2020_04_26_111208_add_backup_limit_to_servers.php | 4 ++-- database/seeds/EggSeeder.php | 1 + 17 files changed, 18 insertions(+), 19 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a28a2d7ee..71328ff02 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,6 +4,7 @@ on: pull_request: jobs: integration_tests: + if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[ci skip]')" runs-on: ubuntu-latest services: mysql: diff --git a/app/Console/Commands/Migration/CleanOrphanedApiKeysCommand.php b/app/Console/Commands/Migration/CleanOrphanedApiKeysCommand.php index b9e007ee7..e7d863992 100644 --- a/app/Console/Commands/Migration/CleanOrphanedApiKeysCommand.php +++ b/app/Console/Commands/Migration/CleanOrphanedApiKeysCommand.php @@ -38,7 +38,7 @@ class CleanOrphanedApiKeysCommand extends Command /** * Delete all orphaned API keys from the database when upgrading from 0.6 to 0.7. * - * @return null|void + * @return void|null */ public function handle() { diff --git a/app/Contracts/Repository/SessionRepositoryInterface.php b/app/Contracts/Repository/SessionRepositoryInterface.php index 496fa35e7..e1dafed1e 100644 --- a/app/Contracts/Repository/SessionRepositoryInterface.php +++ b/app/Contracts/Repository/SessionRepositoryInterface.php @@ -19,7 +19,7 @@ interface SessionRepositoryInterface extends RepositoryInterface * * @param int $user * @param string $session - * @return null|int + * @return int|null */ public function deleteUserSession(int $user, string $session); } diff --git a/app/Contracts/Repository/TaskRepositoryInterface.php b/app/Contracts/Repository/TaskRepositoryInterface.php index 11cb704e1..ea9486254 100644 --- a/app/Contracts/Repository/TaskRepositoryInterface.php +++ b/app/Contracts/Repository/TaskRepositoryInterface.php @@ -21,7 +21,7 @@ interface TaskRepositoryInterface extends RepositoryInterface * * @param int $schedule * @param int $index - * @return null|\Pterodactyl\Models\Task + * @return \Pterodactyl\Models\Task|null */ public function getNextTask(int $schedule, int $index); } diff --git a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php index 3ae474baf..8e23db439 100644 --- a/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php +++ b/app/Http/Requests/Api/Application/Nodes/StoreNodeRequest.php @@ -21,7 +21,7 @@ class StoreNodeRequest extends ApplicationApiRequest /** * Validation rules to apply to this request. * - * @param null|array $rules + * @param array|null $rules * @return array */ public function rules(array $rules = null): array diff --git a/app/Repositories/Concerns/Searchable.php b/app/Repositories/Concerns/Searchable.php index 26ed6544a..8bb11a552 100644 --- a/app/Repositories/Concerns/Searchable.php +++ b/app/Repositories/Concerns/Searchable.php @@ -7,7 +7,7 @@ trait Searchable /** * The search term to use when filtering results. * - * @var null|string + * @var string|null */ protected $searchTerm; diff --git a/app/Repositories/Eloquent/DatabaseRepository.php b/app/Repositories/Eloquent/DatabaseRepository.php index df9dbb6ee..48dec217b 100644 --- a/app/Repositories/Eloquent/DatabaseRepository.php +++ b/app/Repositories/Eloquent/DatabaseRepository.php @@ -140,7 +140,7 @@ class DatabaseRepository extends EloquentRepository implements DatabaseRepositor */ public function createUser(string $username, string $remote, string $password, $max_connections): bool { - if (!$max_connections) { + if (! $max_connections) { return $this->run(sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'', $username, $remote, $password)); } else { return $this->run(sprintf('CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\' WITH MAX_USER_CONNECTIONS %s', $username, $remote, $password, $max_connections)); diff --git a/app/Repositories/Eloquent/SessionRepository.php b/app/Repositories/Eloquent/SessionRepository.php index 50d9d3362..54c4ff629 100644 --- a/app/Repositories/Eloquent/SessionRepository.php +++ b/app/Repositories/Eloquent/SessionRepository.php @@ -34,7 +34,7 @@ class SessionRepository extends EloquentRepository implements SessionRepositoryI * * @param int $user * @param string $session - * @return null|int + * @return int|null */ public function deleteUserSession(int $user, string $session) { diff --git a/app/Repositories/Eloquent/TaskRepository.php b/app/Repositories/Eloquent/TaskRepository.php index 0c1202f59..3b43221e8 100644 --- a/app/Repositories/Eloquent/TaskRepository.php +++ b/app/Repositories/Eloquent/TaskRepository.php @@ -41,7 +41,7 @@ class TaskRepository extends EloquentRepository implements TaskRepositoryInterfa * * @param int $schedule * @param int $index - * @return null|\Pterodactyl\Models\Task + * @return \Pterodactyl\Models\Task|null */ public function getNextTask(int $schedule, int $index) { diff --git a/app/Services/Helpers/AssetHashService.php b/app/Services/Helpers/AssetHashService.php index c3d4c98ff..f9b6a2cd1 100644 --- a/app/Services/Helpers/AssetHashService.php +++ b/app/Services/Helpers/AssetHashService.php @@ -24,7 +24,7 @@ class AssetHashService private $application; /** - * @var null|array + * @var array|null */ protected static $manifest; diff --git a/bootstrap/tests.php b/bootstrap/tests.php index c1a6c8fb0..e22334625 100644 --- a/bootstrap/tests.php +++ b/bootstrap/tests.php @@ -27,7 +27,7 @@ if (config('database.default') !== 'testing') { * Perform database migrations and reseeding before continuing with * running the tests. */ -if (!env('SKIP_MIGRATIONS')) { +if (! env('SKIP_MIGRATIONS')) { $output->writeln(PHP_EOL . 'Refreshing database for Integration tests...'); $kernel->call('migrate:fresh', ['--database' => 'testing']); diff --git a/config/logging.php b/config/logging.php index 33b6a5d0e..7da06f407 100644 --- a/config/logging.php +++ b/config/logging.php @@ -3,7 +3,6 @@ use Monolog\Handler\StreamHandler; return [ - /* |-------------------------------------------------------------------------- | Default Log Channel @@ -77,5 +76,4 @@ return [ 'level' => 'debug', ], ], - ]; diff --git a/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php b/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php index 042e7564c..89e110228 100644 --- a/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php +++ b/database/migrations/2017_07_24_194433_DeleteTaskWhenParentServerIsDeleted.php @@ -23,6 +23,5 @@ class DeleteTaskWhenParentServerIsDeleted extends Migration */ public function down() { - // } } diff --git a/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php b/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php index c1787a9b3..dfd55fb42 100644 --- a/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php +++ b/database/migrations/2020_04_17_203438_allow_nullable_descriptions.php @@ -1,8 +1,8 @@ getContents()); if (json_last_error() !== JSON_ERROR_NONE) { $this->command->error('JSON decode exception for ' . $file->getFilename() . ': ' . json_last_error_msg()); + return; } From 655a751ef362c4c20616d94602f6682afcd78200 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 29 Jun 2020 20:34:58 -0700 Subject: [PATCH 56/63] Prefix command with sudo --- resources/views/admin/nodes/view/configuration.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/admin/nodes/view/configuration.blade.php b/resources/views/admin/nodes/view/configuration.blade.php index 370c0c67d..aa085ce81 100644 --- a/resources/views/admin/nodes/view/configuration.blade.php +++ b/resources/views/admin/nodes/view/configuration.blade.php @@ -74,7 +74,7 @@ swal({ type: 'success', title: 'Token created.', - text: '

To auto-configure your node run the following command:

cd /etc/pterodactyl && ./wings configure --panel-url {{ config('app.url') }} --token ' + data.token + ' --node ' + data.node + '{{ config('app.debug') ? ' --allow-insecure' : '' }}

', + text: '

To auto-configure your node run the following command:

cd /etc/pterodactyl && sudo ./wings configure --panel-url {{ config('app.url') }} --token ' + data.token + ' --node ' + data.node + '{{ config('app.debug') ? ' --allow-insecure' : '' }}

', html: true }) }).fail(function () { From fde8465f35fca0391b293c5273ef81167e673d47 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 30 Jun 2020 20:05:11 -0700 Subject: [PATCH 57/63] Show a better error when JSON data cannot be parsed in the request --- app/Http/Kernel.php | 3 ++ app/Http/Middleware/Api/IsValidJson.php | 38 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 app/Http/Middleware/Api/IsValidJson.php diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index b211ae598..994fc0824 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -9,6 +9,7 @@ use Pterodactyl\Http\Middleware\TrimStrings; use Pterodactyl\Http\Middleware\TrustProxies; use Illuminate\Session\Middleware\StartSession; use Pterodactyl\Http\Middleware\EncryptCookies; +use Pterodactyl\Http\Middleware\Api\IsValidJson; use Pterodactyl\Http\Middleware\VerifyCsrfToken; use Pterodactyl\Http\Middleware\VerifyReCaptcha; use Pterodactyl\Http\Middleware\AdminAuthenticate; @@ -69,6 +70,7 @@ class Kernel extends HttpKernel ], 'api' => [ 'throttle:240,1', + IsValidJson::class, ApiSubstituteBindings::class, SetSessionDriver::class, 'api..key:' . ApiKey::TYPE_APPLICATION, @@ -80,6 +82,7 @@ class Kernel extends HttpKernel StartSession::class, SetSessionDriver::class, AuthenticateSession::class, + IsValidJson::class, SubstituteClientApiBindings::class, 'api..key:' . ApiKey::TYPE_ACCOUNT, AuthenticateIPAccess::class, diff --git a/app/Http/Middleware/Api/IsValidJson.php b/app/Http/Middleware/Api/IsValidJson.php new file mode 100644 index 000000000..20c54dab4 --- /dev/null +++ b/app/Http/Middleware/Api/IsValidJson.php @@ -0,0 +1,38 @@ +isJson() && ! empty($request->getContent())) { + json_decode($request->getContent(), true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new BadRequestHttpException( + sprintf( + 'The JSON data passed in the request appears to be malformed. err_code: %d err_message: "%s"', + json_last_error(), + json_last_error_msg() + ) + ); + } + } + + return $next($request); + } +} From e95a532da958bcf654dafcdff403a2b423426423 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 2 Jul 2020 21:11:16 -0700 Subject: [PATCH 58/63] Make rate limit configurable; closes #1695 --- app/Http/Kernel.php | 2 -- app/Providers/RouteServiceProvider.php | 10 ++++++++-- config/http.php | 21 +++++++++++++++++++++ config/pterodactyl.php | 4 +++- 4 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 config/http.php diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 994fc0824..fa4f8a38c 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -69,7 +69,6 @@ class Kernel extends HttpKernel RequireTwoFactorAuthentication::class, ], 'api' => [ - 'throttle:240,1', IsValidJson::class, ApiSubstituteBindings::class, SetSessionDriver::class, @@ -78,7 +77,6 @@ class Kernel extends HttpKernel AuthenticateIPAccess::class, ], 'client-api' => [ - 'throttle:240,1', StartSession::class, SetSessionDriver::class, AuthenticateSession::class, diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index e6cb8b169..0ea33b5da 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -38,11 +38,17 @@ class RouteServiceProvider extends ServiceProvider ->namespace($this->namespace . '\Server') ->group(base_path('routes/server.php')); - Route::middleware(['api'])->prefix('/api/application') + Route::middleware([ + sprintf('throttle:%s,%s', config('http.rate_limit.application'), config('http.rate_limit.application_period')), + 'api', + ])->prefix('/api/application') ->namespace($this->namespace . '\Api\Application') ->group(base_path('routes/api-application.php')); - Route::middleware(['client-api'])->prefix('/api/client') + Route::middleware([ + sprintf('throttle:%s,%s', config('http.rate_limit.client'), config('http.rate_limit.client_period')), + 'client-api', + ])->prefix('/api/client') ->namespace($this->namespace . '\Api\Client') ->group(base_path('routes/api-client.php')); diff --git a/config/http.php b/config/http.php new file mode 100644 index 000000000..b39845239 --- /dev/null +++ b/config/http.php @@ -0,0 +1,21 @@ + [ + 'client_period' => 1, + 'client' => env('APP_API_CLIENT_RATELIMIT', 240), + + 'application_period' => 1, + 'application' => env('APP_API_APPLICATION_RATELIMIT', 240), + ], +]; diff --git a/config/pterodactyl.php b/config/pterodactyl.php index 92404cd98..70014bc0a 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -223,5 +223,7 @@ return [ | | 'P_SERVER_CREATED_AT' => 'created_at' */ - 'environment_variables' => [], + 'environment_variables' => [ + 'P_SERVER_ALLOCATION_LIMIT' => 'allocation_limit', + ], ]; From 7ee509d8c205801c8197c7102901e3cb4d53c98e Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 2 Jul 2020 21:14:53 -0700 Subject: [PATCH 59/63] urlencode company name; closes #1690 --- app/Services/Users/TwoFactorSetupService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/Users/TwoFactorSetupService.php b/app/Services/Users/TwoFactorSetupService.php index 032a43083..5dcb7879c 100644 --- a/app/Services/Users/TwoFactorSetupService.php +++ b/app/Services/Users/TwoFactorSetupService.php @@ -71,7 +71,7 @@ class TwoFactorSetupService 'totp_secret' => $this->encrypter->encrypt($secret), ]); - $company = preg_replace('/\s/', '', $this->config->get('app.name')); + $company = urlencode(preg_replace('/\s/', '', $this->config->get('app.name'))); return sprintf( 'otpauth://totp/%1$s:%2$s?secret=%3$s&issuer=%1$s', From a998b463e30f565dca789083c7083ce65a836480 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 2 Jul 2020 21:55:25 -0700 Subject: [PATCH 60/63] Generate recovery tokens when enabling 2FA on an account --- .../Api/Client/TwoFactorController.php | 9 +++- app/Models/RecoveryToken.php | 39 +++++++++++++++ app/Models/User.php | 9 ++++ .../Eloquent/RecoveryTokenRepository.php | 16 ++++++ app/Services/Users/ToggleTwoFactorService.php | 49 ++++++++++++++++--- ...3612_create_user_recovery_tokens_table.php | 31 ++++++++++++ 6 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 app/Models/RecoveryToken.php create mode 100644 app/Repositories/Eloquent/RecoveryTokenRepository.php create mode 100644 database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php diff --git a/app/Http/Controllers/Api/Client/TwoFactorController.php b/app/Http/Controllers/Api/Client/TwoFactorController.php index 8c8acfdf4..93be78fbc 100644 --- a/app/Http/Controllers/Api/Client/TwoFactorController.php +++ b/app/Http/Controllers/Api/Client/TwoFactorController.php @@ -96,9 +96,14 @@ class TwoFactorController extends ClientApiController throw new ValidationException($validator); } - $this->toggleTwoFactorService->handle($request->user(), $request->input('code'), true); + $tokens = $this->toggleTwoFactorService->handle($request->user(), $request->input('code'), true); - return new JsonResponse([], Response::HTTP_NO_CONTENT); + return new JsonResponse([ + 'object' => 'recovery_tokens', + 'attributes' => [ + 'tokens' => $tokens, + ], + ]); } /** diff --git a/app/Models/RecoveryToken.php b/app/Models/RecoveryToken.php new file mode 100644 index 000000000..7be74f53c --- /dev/null +++ b/app/Models/RecoveryToken.php @@ -0,0 +1,39 @@ + 'required|string', + ]; + + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index c93fae6dd..47334ccee 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -39,6 +39,7 @@ use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; * @property \Pterodactyl\Models\ApiKey[]|\Illuminate\Database\Eloquent\Collection $apiKeys * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers * @property \Pterodactyl\Models\DaemonKey[]|\Illuminate\Database\Eloquent\Collection $keys + * @property \Pterodactyl\Models\RecoveryToken[]|\Illuminate\Database\Eloquent\Collection $recoveryCodes */ class User extends Model implements AuthenticatableContract, @@ -251,4 +252,12 @@ class User extends Model implements return $this->hasMany(ApiKey::class) ->where('key_type', ApiKey::TYPE_ACCOUNT); } + + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function recoveryCodes() + { + return $this->hasMany(RecoveryToken::class); + } } diff --git a/app/Repositories/Eloquent/RecoveryTokenRepository.php b/app/Repositories/Eloquent/RecoveryTokenRepository.php new file mode 100644 index 000000000..5dfeeacfe --- /dev/null +++ b/app/Repositories/Eloquent/RecoveryTokenRepository.php @@ -0,0 +1,16 @@ +encrypter = $encrypter; $this->google2FA = $google2FA; $this->repository = $repository; + $this->recoveryTokenRepository = $recoveryTokenRepository; } /** @@ -49,7 +59,7 @@ class ToggleTwoFactorService * @param \Pterodactyl\Models\User $user * @param string $token * @param bool|null $toggleState - * @return bool + * @return string[] * * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException @@ -58,16 +68,43 @@ class ToggleTwoFactorService * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid */ - public function handle(User $user, string $token, bool $toggleState = null): bool + public function handle(User $user, string $token, bool $toggleState = null): array { $secret = $this->encrypter->decrypt($user->totp_secret); $isValidToken = $this->google2FA->verifyKey($secret, $token, config()->get('pterodactyl.auth.2fa.window')); if (! $isValidToken) { - throw new TwoFactorAuthenticationTokenInvalid( - 'The token provided is not valid.' - ); + throw new TwoFactorAuthenticationTokenInvalid('The token provided is not valid.'); + } + + // Now that we're enabling 2FA on the account, generate 10 recovery tokens for the account + // and store them hashed in the database. We'll return them to the caller so that the user + // can see and save them. + // + // If a user is unable to login with a 2FA token they can provide one of these backup codes + // which will then be marked as deleted from the database and will also bypass 2FA protections + // on their account. + $tokens = []; + if ((! $toggleState && ! $user->use_totp) || $toggleState) { + $inserts = []; + for ($i = 0; $i < 10; $i++) { + $token = Str::random(10); + + $inserts[] = [ + 'user_id' => $user->id, + 'token' => password_hash($token, PASSWORD_DEFAULT), + ]; + + $tokens[] = $token; + } + + // Bulk insert the hashed tokens. + $this->recoveryTokenRepository->insert($inserts); + } elseif ($toggleState === false || $user->use_totp) { + // If we are disabling 2FA on this account we will delete all of the recovery codes + // that exist in the database for this account. + $this->recoveryTokenRepository->deleteWhere(['user_id' => $user->id]); } $this->repository->withoutFreshModel()->update($user->id, [ @@ -75,6 +112,6 @@ class ToggleTwoFactorService 'use_totp' => (is_null($toggleState) ? ! $user->use_totp : $toggleState), ]); - return true; + return $tokens; } } diff --git a/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php b/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php new file mode 100644 index 000000000..cca85e67f --- /dev/null +++ b/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php @@ -0,0 +1,31 @@ +id(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('recovery_tokens'); + } +} From c522935403606c704cc0e0d3c9691ee33327d723 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 2 Jul 2020 22:11:07 -0700 Subject: [PATCH 61/63] Fix logic when generating recovery codes and update migration --- app/Services/Users/ToggleTwoFactorService.php | 74 +++++++++++-------- ...3612_create_user_recovery_tokens_table.php | 6 +- 2 files changed, 47 insertions(+), 33 deletions(-) diff --git a/app/Services/Users/ToggleTwoFactorService.php b/app/Services/Users/ToggleTwoFactorService.php index 23ebf166e..f8b41b454 100644 --- a/app/Services/Users/ToggleTwoFactorService.php +++ b/app/Services/Users/ToggleTwoFactorService.php @@ -6,6 +6,7 @@ use Carbon\Carbon; use Illuminate\Support\Str; use Pterodactyl\Models\User; use PragmaRX\Google2FA\Google2FA; +use Illuminate\Database\ConnectionInterface; use Illuminate\Contracts\Encryption\Encrypter; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Pterodactyl\Repositories\Eloquent\RecoveryTokenRepository; @@ -33,15 +34,22 @@ class ToggleTwoFactorService */ private $recoveryTokenRepository; + /** + * @var \Illuminate\Database\ConnectionInterface + */ + private $connection; + /** * ToggleTwoFactorService constructor. * + * @param \Illuminate\Database\ConnectionInterface $connection * @param \Illuminate\Contracts\Encryption\Encrypter $encrypter * @param \PragmaRX\Google2FA\Google2FA $google2FA * @param \Pterodactyl\Repositories\Eloquent\RecoveryTokenRepository $recoveryTokenRepository * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository */ public function __construct( + ConnectionInterface $connection, Encrypter $encrypter, Google2FA $google2FA, RecoveryTokenRepository $recoveryTokenRepository, @@ -51,6 +59,7 @@ class ToggleTwoFactorService $this->google2FA = $google2FA; $this->repository = $repository; $this->recoveryTokenRepository = $recoveryTokenRepository; + $this->connection = $connection; } /** @@ -61,11 +70,10 @@ class ToggleTwoFactorService * @param bool|null $toggleState * @return string[] * + * @throws \Throwable * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid */ public function handle(User $user, string $token, bool $toggleState = null): array @@ -78,40 +86,42 @@ class ToggleTwoFactorService throw new TwoFactorAuthenticationTokenInvalid('The token provided is not valid.'); } - // Now that we're enabling 2FA on the account, generate 10 recovery tokens for the account - // and store them hashed in the database. We'll return them to the caller so that the user - // can see and save them. - // - // If a user is unable to login with a 2FA token they can provide one of these backup codes - // which will then be marked as deleted from the database and will also bypass 2FA protections - // on their account. - $tokens = []; - if ((! $toggleState && ! $user->use_totp) || $toggleState) { - $inserts = []; - for ($i = 0; $i < 10; $i++) { - $token = Str::random(10); + return $this->connection->transaction(function () use ($user, $toggleState) { + // Now that we're enabling 2FA on the account, generate 10 recovery tokens for the account + // and store them hashed in the database. We'll return them to the caller so that the user + // can see and save them. + // + // If a user is unable to login with a 2FA token they can provide one of these backup codes + // which will then be marked as deleted from the database and will also bypass 2FA protections + // on their account. + $tokens = []; + if ((! $toggleState && ! $user->use_totp) || $toggleState) { + $inserts = []; + for ($i = 0; $i < 10; $i++) { + $token = Str::random(10); - $inserts[] = [ - 'user_id' => $user->id, - 'token' => password_hash($token, PASSWORD_DEFAULT), - ]; + $inserts[] = [ + 'user_id' => $user->id, + 'token' => password_hash($token, PASSWORD_DEFAULT), + ]; - $tokens[] = $token; + $tokens[] = $token; + } + + // Before inserting any new records make sure all of the old ones are deleted to avoid + // any issues or storing an unnecessary number of tokens in the database. + $this->recoveryTokenRepository->deleteWhere(['user_id' => $user->id]); + + // Bulk insert the hashed tokens. + $this->recoveryTokenRepository->insert($inserts); } - // Bulk insert the hashed tokens. - $this->recoveryTokenRepository->insert($inserts); - } elseif ($toggleState === false || $user->use_totp) { - // If we are disabling 2FA on this account we will delete all of the recovery codes - // that exist in the database for this account. - $this->recoveryTokenRepository->deleteWhere(['user_id' => $user->id]); - } + $this->repository->withoutFreshModel()->update($user->id, [ + 'totp_authenticated_at' => Carbon::now(), + 'use_totp' => (is_null($toggleState) ? ! $user->use_totp : $toggleState), + ]); - $this->repository->withoutFreshModel()->update($user->id, [ - 'totp_authenticated_at' => Carbon::now(), - 'use_totp' => (is_null($toggleState) ? ! $user->use_totp : $toggleState), - ]); - - return $tokens; + return $tokens; + }); } } diff --git a/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php b/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php index cca85e67f..df0f34919 100644 --- a/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php +++ b/database/migrations/2020_07_02_213612_create_user_recovery_tokens_table.php @@ -15,7 +15,11 @@ class CreateUserRecoveryTokensTable extends Migration { Schema::create('recovery_tokens', function (Blueprint $table) { $table->id(); - $table->timestamps(); + $table->unsignedInteger('user_id'); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + + $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete(); }); } From 795e045950718819921f0d7933939eaf7889bfda Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 2 Jul 2020 22:23:25 -0700 Subject: [PATCH 62/63] Display generated recovery tokens when enabling two factor --- .../api/account/enableAccountTwoFactor.ts | 10 +- .../dashboard/forms/SetupTwoFactorModal.tsx | 121 +++++++++++------- 2 files changed, 82 insertions(+), 49 deletions(-) diff --git a/resources/scripts/api/account/enableAccountTwoFactor.ts b/resources/scripts/api/account/enableAccountTwoFactor.ts index d44d09acb..e7a15f62d 100644 --- a/resources/scripts/api/account/enableAccountTwoFactor.ts +++ b/resources/scripts/api/account/enableAccountTwoFactor.ts @@ -1,9 +1,7 @@ import http from '@/api/http'; -export default (code: string): Promise => { - return new Promise((resolve, reject) => { - http.post('/api/client/account/two-factor', { code }) - .then(() => resolve()) - .catch(reject); - }); +export default async (code: string): Promise => { + const { data } = await http.post('/api/client/account/two-factor', { code }); + + return data.attributes.tokens; }; diff --git a/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx b/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx index 028b2b361..ff5eeec7f 100644 --- a/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx +++ b/resources/scripts/components/dashboard/forms/SetupTwoFactorModal.tsx @@ -2,21 +2,22 @@ import React, { useEffect, useState } from 'react'; import Modal, { RequiredModalProps } from '@/components/elements/Modal'; import { Form, Formik, FormikHelpers } from 'formik'; import { object, string } from 'yup'; -import Field from '@/components/elements/Field'; import getTwoFactorTokenUrl from '@/api/account/getTwoFactorTokenUrl'; import enableAccountTwoFactor from '@/api/account/enableAccountTwoFactor'; -import FlashMessageRender from '@/components/FlashMessageRender'; import { Actions, useStoreActions } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import { httpErrorToHuman } from '@/api/http'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import Field from '@/components/elements/Field'; interface Values { code: string; } -export default ({ ...props }: RequiredModalProps) => { +export default ({ onDismissed, ...props }: RequiredModalProps) => { const [ token, setToken ] = useState(''); const [ loading, setLoading ] = useState(true); + const [ recoveryTokens, setRecoveryTokens ] = useState([]); const updateUserData = useStoreActions((actions: Actions) => actions.user.updateUserData); const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); @@ -27,22 +28,30 @@ export default ({ ...props }: RequiredModalProps) => { .then(setToken) .catch(error => { console.error(error); + addError({ message: httpErrorToHuman(error), key: 'account:two-factor' }); }); }, []); const submit = ({ code }: Values, { setSubmitting }: FormikHelpers) => { clearFlashes('account:two-factor'); enableAccountTwoFactor(code) - .then(() => { - updateUserData({ useTotp: true }); - props.onDismissed(); + .then(tokens => { + setRecoveryTokens(tokens); }) .catch(error => { console.error(error); addError({ message: httpErrorToHuman(error), key: 'account:two-factor' }); - setSubmitting(false); - }); + }) + .then(() => setSubmitting(false)); + }; + + const dismiss = () => { + if (recoveryTokens.length > 0) { + updateUserData({ useTotp: true }); + } + + onDismissed(); }; return ( @@ -58,47 +67,73 @@ export default ({ ...props }: RequiredModalProps) => { {({ isSubmitting, isValid }) => ( -
- -
-
-
- {!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})}
+                            
+
+ +
+ + : + + +
+
+
+ {!token || !token.length ? + + : + setLoading(false)} + className={'w-full h-full shadow-none rounded-0'} + /> + } +
+
+
+
+ - : - setLoading(false)} - className={'w-full h-full shadow-none rounded-0'} - /> - } +
+
+ +
-
-
- -
-
- -
-
-
- + + } )} From 7b75e7a648455ceebec3459d3144fb8cd8bf8ed7 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 2 Jul 2020 23:01:02 -0700 Subject: [PATCH 63/63] Support using recovery tokens during the login process to bypass 2fa; closes #479 --- .../Auth/AbstractLoginController.php | 9 ++-- .../Auth/LoginCheckpointController.php | 35 +++++++++++++--- app/Http/Controllers/Auth/LoginController.php | 2 +- .../Requests/Auth/LoginCheckpointRequest.php | 16 +++++++- app/Models/User.php | 4 +- resources/scripts/api/auth/loginCheckpoint.ts | 7 ++-- .../auth/LoginCheckpointContainer.tsx | 41 ++++++++++++------- 7 files changed, 84 insertions(+), 30 deletions(-) diff --git a/app/Http/Controllers/Auth/AbstractLoginController.php b/app/Http/Controllers/Auth/AbstractLoginController.php index 0810d7e93..b24a1a62a 100644 --- a/app/Http/Controllers/Auth/AbstractLoginController.php +++ b/app/Http/Controllers/Auth/AbstractLoginController.php @@ -68,10 +68,11 @@ abstract class AbstractLoginController extends Controller * * @param \Illuminate\Http\Request $request * @param \Illuminate\Contracts\Auth\Authenticatable|null $user + * @param string|null $message * * @throws \Pterodactyl\Exceptions\DisplayException */ - protected function sendFailedLoginResponse(Request $request, Authenticatable $user = null) + protected function sendFailedLoginResponse(Request $request, Authenticatable $user = null, string $message = null) { $this->incrementLoginAttempts($request); $this->fireFailedLoginEvent($user, [ @@ -79,7 +80,9 @@ abstract class AbstractLoginController extends Controller ]); if ($request->route()->named('auth.login-checkpoint')) { - throw new DisplayException(trans('auth.two_factor.checkpoint_failed')); + throw new DisplayException( + $message ?? trans('auth.two_factor.checkpoint_failed') + ); } throw new DisplayException(trans('auth.failed')); @@ -116,7 +119,7 @@ abstract class AbstractLoginController extends Controller */ protected function getField(string $input = null): string { - return str_contains($input, '@') ? 'email' : 'username'; + return ($input && str_contains($input, '@')) ? 'email' : 'username'; } /** diff --git a/app/Http/Controllers/Auth/LoginCheckpointController.php b/app/Http/Controllers/Auth/LoginCheckpointController.php index 1cc2fe1af..c44f18a81 100644 --- a/app/Http/Controllers/Auth/LoginCheckpointController.php +++ b/app/Http/Controllers/Auth/LoginCheckpointController.php @@ -11,6 +11,7 @@ use Pterodactyl\Http\Requests\Auth\LoginCheckpointRequest; use Illuminate\Contracts\Cache\Repository as CacheRepository; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; +use Pterodactyl\Repositories\Eloquent\RecoveryTokenRepository; class LoginCheckpointController extends AbstractLoginController { @@ -34,6 +35,11 @@ class LoginCheckpointController extends AbstractLoginController */ private $encrypter; + /** + * @var \Pterodactyl\Repositories\Eloquent\RecoveryTokenRepository + */ + private $recoveryTokenRepository; + /** * LoginCheckpointController constructor. * @@ -42,6 +48,7 @@ class LoginCheckpointController extends AbstractLoginController * @param \PragmaRX\Google2FA\Google2FA $google2FA * @param \Illuminate\Contracts\Config\Repository $config * @param \Illuminate\Contracts\Cache\Repository $cache + * @param \Pterodactyl\Repositories\Eloquent\RecoveryTokenRepository $recoveryTokenRepository * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository */ public function __construct( @@ -50,6 +57,7 @@ class LoginCheckpointController extends AbstractLoginController Google2FA $google2FA, Repository $config, CacheRepository $cache, + RecoveryTokenRepository $recoveryTokenRepository, UserRepositoryInterface $repository ) { parent::__construct($auth, $config); @@ -58,6 +66,7 @@ class LoginCheckpointController extends AbstractLoginController $this->cache = $cache; $this->repository = $repository; $this->encrypter = $encrypter; + $this->recoveryTokenRepository = $recoveryTokenRepository; } /** @@ -76,21 +85,35 @@ class LoginCheckpointController extends AbstractLoginController public function __invoke(LoginCheckpointRequest $request): JsonResponse { $token = $request->input('confirmation_token'); + $recoveryToken = $request->input('recovery_token'); try { + /** @var \Pterodactyl\Models\User $user */ $user = $this->repository->find($this->cache->get($token, 0)); } catch (RecordNotFoundException $exception) { - return $this->sendFailedLoginResponse($request); + return $this->sendFailedLoginResponse($request, null, 'The authentication token provided has expired, please refresh the page and try again.'); } - $decrypted = $this->encrypter->decrypt($user->totp_secret); + // If we got a recovery token try to find one that matches for the user and then continue + // through the process (and delete the token). + if (! is_null($recoveryToken)) { + foreach ($user->recoveryTokens as $token) { + if (password_verify($recoveryToken, $token->token)) { + $this->recoveryTokenRepository->delete($token->id); - if ($this->google2FA->verifyKey($decrypted, (string) $request->input('authentication_code') ?? '', config('pterodactyl.auth.2fa.window'))) { - $this->cache->delete($token); + return $this->sendLoginResponse($user, $request); + } + } + } else { + $decrypted = $this->encrypter->decrypt($user->totp_secret); - return $this->sendLoginResponse($user, $request); + if ($this->google2FA->verifyKey($decrypted, (string) $request->input('authentication_code') ?? '', config('pterodactyl.auth.2fa.window'))) { + $this->cache->delete($token); + + return $this->sendLoginResponse($user, $request); + } } - return $this->sendFailedLoginResponse($request, $user); + return $this->sendFailedLoginResponse($request, $user, ! empty($recoveryToken) ? 'The recovery token provided is not valid.' : null); } } diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 0d7f21978..593189db1 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -103,7 +103,7 @@ class LoginController extends AbstractLoginController $token = Str::random(64); $this->cache->put($token, $user->id, Chronos::now()->addMinutes(5)); - return JsonResponse::create([ + return new JsonResponse([ 'data' => [ 'complete' => false, 'confirmation_token' => $token, diff --git a/app/Http/Requests/Auth/LoginCheckpointRequest.php b/app/Http/Requests/Auth/LoginCheckpointRequest.php index 158f5c465..87d84ce97 100644 --- a/app/Http/Requests/Auth/LoginCheckpointRequest.php +++ b/app/Http/Requests/Auth/LoginCheckpointRequest.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Requests\Auth; +use Illuminate\Validation\Rule; use Illuminate\Foundation\Http\FormRequest; class LoginCheckpointRequest extends FormRequest @@ -25,7 +26,20 @@ class LoginCheckpointRequest extends FormRequest { return [ 'confirmation_token' => 'required|string', - 'authentication_code' => 'required|numeric', + 'authentication_code' => [ + 'nullable', + 'numeric', + Rule::requiredIf(function () { + return empty($this->input('recovery_token')); + }), + ], + 'recovery_token' => [ + 'nullable', + 'string', + Rule::requiredIf(function () { + return empty($this->input('authentication_code')); + }), + ], ]; } } diff --git a/app/Models/User.php b/app/Models/User.php index 47334ccee..408ceead3 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -39,7 +39,7 @@ use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; * @property \Pterodactyl\Models\ApiKey[]|\Illuminate\Database\Eloquent\Collection $apiKeys * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers * @property \Pterodactyl\Models\DaemonKey[]|\Illuminate\Database\Eloquent\Collection $keys - * @property \Pterodactyl\Models\RecoveryToken[]|\Illuminate\Database\Eloquent\Collection $recoveryCodes + * @property \Pterodactyl\Models\RecoveryToken[]|\Illuminate\Database\Eloquent\Collection $recoveryTokens */ class User extends Model implements AuthenticatableContract, @@ -256,7 +256,7 @@ class User extends Model implements /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function recoveryCodes() + public function recoveryTokens() { return $this->hasMany(RecoveryToken::class); } diff --git a/resources/scripts/api/auth/loginCheckpoint.ts b/resources/scripts/api/auth/loginCheckpoint.ts index 244d27c81..25bb715a4 100644 --- a/resources/scripts/api/auth/loginCheckpoint.ts +++ b/resources/scripts/api/auth/loginCheckpoint.ts @@ -1,13 +1,14 @@ import http from '@/api/http'; import { LoginResponse } from '@/api/auth/login'; -export default (token: string, code: string): Promise => { +export default (token: string, code: string, recoveryToken?: string): Promise => { return new Promise((resolve, reject) => { http.post('/auth/login/checkpoint', { - // eslint-disable-next-line @typescript-eslint/camelcase + /* eslint-disable @typescript-eslint/camelcase */ confirmation_token: token, - // eslint-disable-next-line @typescript-eslint/camelcase authentication_code: code, + recovery_token: (recoveryToken && recoveryToken.length > 0) ? recoveryToken : undefined, + /* eslint-enable @typescript-eslint/camelcase */ }) .then(response => resolve({ complete: response.data.data.complete, diff --git a/resources/scripts/components/auth/LoginCheckpointContainer.tsx b/resources/scripts/components/auth/LoginCheckpointContainer.tsx index 5534dd0f7..dddcd0c1c 100644 --- a/resources/scripts/components/auth/LoginCheckpointContainer.tsx +++ b/resources/scripts/components/auth/LoginCheckpointContainer.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Link, RouteComponentProps } from 'react-router-dom'; import loginCheckpoint from '@/api/auth/loginCheckpoint'; import { httpErrorToHuman } from '@/api/http'; @@ -14,6 +14,7 @@ import Field from '@/components/elements/Field'; interface Values { code: string; + recoveryCode: '', } type OwnProps = RouteComponentProps<{}, StaticContext, { token?: string }> @@ -24,7 +25,8 @@ type Props = OwnProps & { } const LoginCheckpointContainer = () => { - const { isSubmitting } = useFormikContext(); + const { isSubmitting, setFieldValue } = useFormikContext(); + const [ isMissingDevice, setIsMissingDevice ] = useState(false); return ( {
@@ -54,6 +60,18 @@ const LoginCheckpointContainer = () => { }
+
+ { + setFieldValue('code', ''); + setFieldValue('recoveryCode', ''); + setIsMissingDevice(s => !s); + }} + className={'cursor-pointer text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700'} + > + {!isMissingDevice ? 'I\'ve Lost My Device' : 'I Have My Device'} + +
{ }; const EnhancedForm = withFormik({ - handleSubmit: ({ code }, { setSubmitting, props: { addError, clearFlashes, location } }) => { + handleSubmit: ({ code, recoveryCode }, { setSubmitting, props: { addError, clearFlashes, location } }) => { clearFlashes(); - console.log(location.state.token, code); - loginCheckpoint(location.state?.token || '', code) + loginCheckpoint(location.state?.token || '', code, recoveryCode) .then(response => { if (response.complete) { // @ts-ignore @@ -89,11 +106,7 @@ const EnhancedForm = withFormik({ mapPropsToValues: () => ({ code: '', - }), - - validationSchema: object().shape({ - code: string().required('An authentication code must be provided.') - .length(6, 'Authentication code must be 6 digits in length.'), + recoveryCode: '', }), })(LoginCheckpointContainer);