This commit is contained in:
Saeed Vaziry 2024-10-13 12:33:12 +02:00
parent 386d8e73a7
commit 224e0ac2b0
49 changed files with 3668 additions and 766 deletions

View File

@ -3,7 +3,9 @@ name: code-style
on: on:
push: push:
branches: branches:
- main
- 1.x - 1.x
- 2.x
pull_request: pull_request:
jobs: jobs:
@ -13,8 +15,8 @@ jobs:
strategy: strategy:
fail-fast: true fail-fast: true
matrix: matrix:
php: [8.2] php: [ 8.2 ]
node-version: ["20.x"] node-version: [ "20.x" ]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@ -3,7 +3,9 @@ name: tests
on: on:
push: push:
branches: branches:
- main
- 1.x - 1.x
- 2.x
pull_request: pull_request:
jobs: jobs:
@ -13,7 +15,7 @@ jobs:
strategy: strategy:
fail-fast: true fail-fast: true
matrix: matrix:
php: [8.2] php: [ 8.2 ]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@ -43,7 +43,7 @@ public static function rules(Server $server): array
{ {
return [ return [
'name' => [ 'name' => [
'string', 'required',
'max:255', 'max:255',
Rule::unique('servers')->where('project_id', $server->project_id)->ignore($server->id), Rule::unique('servers')->where('project_id', $server->project_id)->ignore($server->id),
], ],

View File

@ -2,7 +2,6 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
@ -17,16 +16,17 @@ public function handle(): void
$privateKeyPath = storage_path('ssh-private.pem'); $privateKeyPath = storage_path('ssh-private.pem');
$publicKeyPath = storage_path('ssh-public.key'); $publicKeyPath = storage_path('ssh-public.key');
if (File::exists($privateKeyPath) && File::exists($publicKeyPath) && !$this->option('force')) { if (File::exists($privateKeyPath) && File::exists($publicKeyPath) && ! $this->option('force')) {
$this->error('Keys already exist. Use --force to overwrite.'); $this->error('Keys already exist. Use --force to overwrite.');
return; return;
} }
exec('openssl genpkey -algorithm RSA -out ' . $privateKeyPath); exec('openssl genpkey -algorithm RSA -out '.$privateKeyPath);
exec('chmod 600 ' . $privateKeyPath); exec('chmod 600 '.$privateKeyPath);
exec('ssh-keygen -y -f ' . $privateKeyPath . ' > ' . $publicKeyPath); exec('ssh-keygen -y -f '.$privateKeyPath.' > '.$publicKeyPath);
exec('chown -R ' . get_current_user() . ':' . get_current_user() . ' ' . $privateKeyPath); exec('chown -R '.get_current_user().':'.get_current_user().' '.$privateKeyPath);
exec('chown -R ' . get_current_user() . ':' . get_current_user() . ' ' . $publicKeyPath); exec('chown -R '.get_current_user().':'.get_current_user().' '.$publicKeyPath);
$this->info('Keys generated successfully.'); $this->info('Keys generated successfully.');
} }

View File

@ -57,6 +57,15 @@ class Queue extends AbstractModel
QueueStatus::STOPPED => 'gray', QueueStatus::STOPPED => 'gray',
]; ];
public static function boot(): void
{
parent::boot();
static::deleting(function (Queue $queue) {
$queue->server->processManager()->handler()->delete($queue->id, $queue->site_id);
});
}
public function getServerIdAttribute(int $value): int public function getServerIdAttribute(int $value): int
{ {
if (! $value) { if (! $value) {

View File

@ -18,6 +18,7 @@
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany; use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -117,26 +118,33 @@ public static function boot(): void
parent::boot(); parent::boot();
static::deleting(function (Server $server) { static::deleting(function (Server $server) {
$server->sites()->each(function (Site $site) { DB::beginTransaction();
$site->delete(); try {
}); $server->sites()->each(function (Site $site) {
$server->provider()->delete(); $site->delete();
$server->logs()->each(function (ServerLog $log) { });
$log->delete(); $server->logs()->each(function (ServerLog $log) {
}); $log->delete();
$server->services()->delete(); });
$server->databases()->delete(); $server->services()->delete();
$server->databaseUsers()->delete(); $server->databases()->delete();
$server->firewallRules()->delete(); $server->databaseUsers()->delete();
$server->cronJobs()->delete(); $server->firewallRules()->delete();
$server->queues()->delete(); $server->cronJobs()->delete();
$server->daemons()->delete(); $server->queues()->delete();
$server->sshKeys()->detach(); $server->daemons()->delete();
if (File::exists($server->sshKey()['public_key_path'])) { $server->sshKeys()->detach();
File::delete($server->sshKey()['public_key_path']); if (File::exists($server->sshKey()['public_key_path'])) {
} File::delete($server->sshKey()['public_key_path']);
if (File::exists($server->sshKey()['private_key_path'])) { }
File::delete($server->sshKey()['private_key_path']); if (File::exists($server->sshKey()['private_key_path'])) {
File::delete($server->sshKey()['private_key_path']);
}
$server->provider()->delete();
DB::commit();
} catch (\Throwable $e) {
DB::rollBack();
throw $e;
} }
}); });
} }

View File

@ -5,7 +5,11 @@
use App\Actions\Service\Manage; use App\Actions\Service\Manage;
use App\Enums\ServiceStatus; use App\Enums\ServiceStatus;
use App\Exceptions\ServiceInstallationFailed; use App\Exceptions\ServiceInstallationFailed;
use App\SSH\Services\Database\Database as DatabaseAlias;
use App\SSH\Services\PHP\PHP as PHPAlias;
use App\SSH\Services\ProcessManager\ProcessManager;
use App\SSH\Services\ServiceInterface; use App\SSH\Services\ServiceInterface;
use App\SSH\Services\WebServer\WebServer as WebServerAlias;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -76,6 +80,9 @@ public function server(): BelongsTo
return $this->belongsTo(Server::class); return $this->belongsTo(Server::class);
} }
/**
* @return ProcessManager|DatabaseAlias|PHPAlias|WebServerAlias
*/
public function handler(): ServiceInterface public function handler(): ServiceInterface
{ {
$handler = config('core.service_handlers')[$this->name]; $handler = config('core.service_handlers')[$this->name];

View File

@ -88,7 +88,7 @@ public static function boot(): void
parent::boot(); parent::boot();
static::deleting(function (Site $site) { static::deleting(function (Site $site) {
$site->queues()->delete(); $site->queues()->each(fn (Queue $queue) => $queue->delete());
$site->ssls()->delete(); $site->ssls()->delete();
$site->deployments()->delete(); $site->deployments()->delete();
$site->deploymentScript()->delete(); $site->deploymentScript()->delete();

View File

@ -2,6 +2,7 @@
namespace App\Policies; namespace App\Policies;
use App\Enums\SiteFeature;
use App\Models\Queue; use App\Models\Queue;
use App\Models\Server; use App\Models\Server;
use App\Models\Site; use App\Models\Site;
@ -16,6 +17,7 @@ public function viewAny(User $user, Site $site, Server $server): bool
{ {
return ($user->isAdmin() || $server->project->users->contains($user)) && return ($user->isAdmin() || $server->project->users->contains($user)) &&
$server->isReady() && $server->isReady() &&
$site->hasFeature(SiteFeature::QUEUES) &&
$site->isReady(); $site->isReady();
} }
@ -25,6 +27,7 @@ public function view(User $user, Queue $queue, Site $site, Server $server): bool
$site->server_id === $server->id && $site->server_id === $server->id &&
$server->isReady() && $server->isReady() &&
$site->isReady() && $site->isReady() &&
$site->hasFeature(SiteFeature::QUEUES) &&
$queue->site_id === $site->id; $queue->site_id === $site->id;
} }
@ -32,6 +35,7 @@ public function create(User $user, Site $site, Server $server): bool
{ {
return ($user->isAdmin() || $server->project->users->contains($user)) && return ($user->isAdmin() || $server->project->users->contains($user)) &&
$server->isReady() && $server->isReady() &&
$site->hasFeature(SiteFeature::QUEUES) &&
$site->isReady(); $site->isReady();
} }
@ -41,6 +45,7 @@ public function update(User $user, Queue $queue, Site $site, Server $server): bo
$site->server_id === $server->id && $site->server_id === $server->id &&
$server->isReady() && $server->isReady() &&
$site->isReady() && $site->isReady() &&
$site->hasFeature(SiteFeature::QUEUES) &&
$queue->site_id === $site->id; $queue->site_id === $site->id;
} }
@ -50,6 +55,7 @@ public function delete(User $user, Queue $queue, Site $site, Server $server): bo
$site->server_id === $server->id && $site->server_id === $server->id &&
$server->isReady() && $server->isReady() &&
$site->isReady() && $site->isReady() &&
$site->hasFeature(SiteFeature::QUEUES) &&
$queue->site_id === $site->id; $queue->site_id === $site->id;
} }
} }

View File

@ -2,6 +2,7 @@
namespace App\Policies; namespace App\Policies;
use App\Enums\SiteFeature;
use App\Models\Server; use App\Models\Server;
use App\Models\Site; use App\Models\Site;
use App\Models\Ssl; use App\Models\Ssl;
@ -16,6 +17,7 @@ public function viewAny(User $user, Site $site, Server $server): bool
{ {
return ($user->isAdmin() || $server->project->users->contains($user)) && return ($user->isAdmin() || $server->project->users->contains($user)) &&
$server->isReady() && $server->isReady() &&
$site->hasFeature(SiteFeature::SSL) &&
$site->isReady(); $site->isReady();
} }
@ -25,6 +27,7 @@ public function view(User $user, Ssl $ssl, Site $site, Server $server): bool
$site->server_id === $server->id && $site->server_id === $server->id &&
$server->isReady() && $server->isReady() &&
$site->isReady() && $site->isReady() &&
$site->hasFeature(SiteFeature::SSL) &&
$ssl->site_id === $site->id; $ssl->site_id === $site->id;
} }
@ -32,6 +35,7 @@ public function create(User $user, Site $site, Server $server): bool
{ {
return ($user->isAdmin() || $server->project->users->contains($user)) && return ($user->isAdmin() || $server->project->users->contains($user)) &&
$server->isReady() && $server->isReady() &&
$site->hasFeature(SiteFeature::SSL) &&
$site->isReady(); $site->isReady();
} }
@ -41,6 +45,7 @@ public function update(User $user, Ssl $ssl, Site $site, Server $server): bool
$site->server_id === $server->id && $site->server_id === $server->id &&
$server->isReady() && $server->isReady() &&
$site->isReady() && $site->isReady() &&
$site->hasFeature(SiteFeature::SSL) &&
$ssl->site_id === $site->id; $ssl->site_id === $site->id;
} }
@ -50,6 +55,7 @@ public function delete(User $user, Ssl $ssl, Site $site, Server $server): bool
$site->server_id === $server->id && $site->server_id === $server->id &&
$server->isReady() && $server->isReady() &&
$site->isReady() && $site->isReady() &&
$site->hasFeature(SiteFeature::SSL) &&
$ssl->site_id === $site->id; $ssl->site_id === $site->id;
} }
} }

View File

@ -2,6 +2,8 @@
namespace App\SiteTypes; namespace App\SiteTypes;
use App\Enums\SiteFeature;
use App\SSH\Services\Webserver\Webserver;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
class PHPMyAdmin extends PHPSite class PHPMyAdmin extends PHPSite
@ -9,7 +11,7 @@ class PHPMyAdmin extends PHPSite
public function supportedFeatures(): array public function supportedFeatures(): array
{ {
return [ return [
// SiteFeature::SSL,
]; ];
} }
@ -20,7 +22,7 @@ public function createRules(array $input): array
'required', 'required',
Rule::in($this->site->server->installedPHPVersions()), Rule::in($this->site->server->installedPHPVersions()),
], ],
'version' => 'required|string', 'version' => 'required',
]; ];
} }

View File

@ -35,7 +35,10 @@ public function createRules(array $input): array
'title' => 'required', 'title' => 'required',
'username' => 'required', 'username' => 'required',
'password' => 'required', 'password' => 'required',
'email' => 'required|email', 'email' => [
'required',
'email',
],
'database' => [ 'database' => [
'required', 'required',
Rule::unique('databases', 'name')->where(function ($query) { Rule::unique('databases', 'name')->where(function ($query) {

View File

@ -26,9 +26,9 @@ public function getTitle(): string|Htmlable
return $this->script->name.' - Executions'; return $this->script->name.' - Executions';
} }
public static function canAccess(): bool public function mount(): void
{ {
return auth()->user()?->can('view', get_from_route(Script::class, 'script')) ?? false; $this->authorize('view', $this->script);
} }
public function getWidgets(): array public function getWidgets(): array

View File

@ -60,9 +60,9 @@ public function getTable(): Table
return $this->table return $this->table
->heading('') ->heading('')
->actions([ ->actions([
Action::make('view') Action::make('logs')
->hiddenLabel() ->hiddenLabel()
->tooltip('View') ->tooltip('Logs')
->icon('heroicon-o-eye') ->icon('heroicon-o-eye')
->authorize(fn (ScriptExecution $record) => auth()->user()->can('view', $record->serverLog)) ->authorize(fn (ScriptExecution $record) => auth()->user()->can('view', $record->serverLog))
->modalHeading('View Log') ->modalHeading('View Log')

View File

@ -48,7 +48,8 @@ protected function getTableColumns(): array
->sortable(), ->sortable(),
TextColumn::make('created_at') TextColumn::make('created_at')
->label('Installed At') ->label('Installed At')
->formatStateUsing(fn ($record) => $record->created_at_by_timezone), ->formatStateUsing(fn ($record) => $record->created_at_by_timezone)
->sortable(),
]; ];
} }

View File

@ -7,7 +7,6 @@
use App\Web\Pages\Servers\Widgets\ServerDetails; use App\Web\Pages\Servers\Widgets\ServerDetails;
use App\Web\Pages\Servers\Widgets\UpdateServerInfo; use App\Web\Pages\Servers\Widgets\UpdateServerInfo;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\DeleteAction;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
class Settings extends Page class Settings extends Page
@ -40,11 +39,29 @@ public function getWidgets(): array
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
DeleteAction::make() Action::make('delete')
->icon('heroicon-o-trash') ->icon('heroicon-o-trash')
->record($this->server) ->color('danger')
->requiresConfirmation()
->modalHeading('Delete Server') ->modalHeading('Delete Server')
->modalDescription('Once your server is deleted, all of its resources and data will be permanently deleted and can\'t be restored'), ->modalDescription('Once your server is deleted, all of its resources and data will be permanently deleted and can\'t be restored')
->action(function () {
try {
$this->server->delete();
Notification::make()
->success()
->title('Server deleted')
->send();
$this->redirect(Index::getUrl());
} catch (\Throwable $e) {
Notification::make()
->danger()
->title($e->getMessage())
->send();
}
}),
Action::make('reboot') Action::make('reboot')
->color('gray') ->color('gray')
->icon('heroicon-o-arrow-path') ->icon('heroicon-o-arrow-path')

View File

@ -9,6 +9,8 @@
use App\Web\Pages\Settings\SourceControls\Actions\Create; use App\Web\Pages\Settings\SourceControls\Actions\Create;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Forms\Components\Checkbox; use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
@ -53,6 +55,7 @@ protected function getHeaderActions(): array
->slideOver() ->slideOver()
->form([ ->form([
Select::make('type') Select::make('type')
->label('Site Type')
->options( ->options(
collect(config('core.site_types'))->mapWithKeys(fn ($type) => [$type => $type]) collect(config('core.site_types'))->mapWithKeys(fn ($type) => [$type => $type])
) )
@ -119,6 +122,17 @@ protected function getHeaderActions(): array
->label('Run `composer install --no-dev`') ->label('Run `composer install --no-dev`')
->default(false) ->default(false)
->visible(fn (Get $get) => isset(CreateSite::rules($this->server, $get())['composer'])), ->visible(fn (Get $get) => isset(CreateSite::rules($this->server, $get())['composer'])),
// PHPMyAdmin
Select::make('version')
->label('PHPMyAdmin Version')
->validationAttribute('PHPMyAdmin Version')
->options([
'5.2.1' => '5.2.1',
])
->visible(fn (Get $get) => $get('type') === SiteType::PHPMYADMIN)
->rules(fn (Get $get) => CreateSite::rules($this->server, $get())['version']),
// WordPress
$this->wordpressFields(),
]) ])
->action(function (array $data) { ->action(function (array $data) {
$this->authorize('create', [Site::class, $this->server]); $this->authorize('create', [Site::class, $this->server]);
@ -128,7 +142,7 @@ protected function getHeaderActions(): array
try { try {
$site = app(CreateSite::class)->create($this->server, $data); $site = app(CreateSite::class)->create($this->server, $data);
$this->redirect(\App\Web\Pages\Servers\Sites\View::getUrl([ $this->redirect(View::getUrl([
'server' => $this->server, 'server' => $this->server,
'site' => $site, 'site' => $site,
])); ]));
@ -142,4 +156,35 @@ protected function getHeaderActions(): array
->modalSubmitActionLabel('Create Site'), ->modalSubmitActionLabel('Create Site'),
]; ];
} }
private function wordpressFields(): Component
{
return Grid::make()
->columns(3)
->visible(fn (Get $get) => $get('type') === SiteType::WORDPRESS)
->schema([
TextInput::make('title')
->label('Site Title')
->columnSpan(3)
->rules(fn (Get $get) => CreateSite::rules($this->server, $get())['title']),
TextInput::make('email')
->label('WP Admin Email')
->default(auth()->user()?->email)
->rules(fn (Get $get) => CreateSite::rules($this->server, $get())['email']),
TextInput::make('username')
->label('WP Admin Username')
->rules(fn (Get $get) => CreateSite::rules($this->server, $get())['username']),
TextInput::make('password')
->label('WP Admin Password')
->rules(fn (Get $get) => CreateSite::rules($this->server, $get())['password']),
TextInput::make('database')
->helperText('It will create a database with this name')
->rules(fn (Get $get) => CreateSite::rules($this->server, $get())['database']),
TextInput::make('database_user')
->helperText('It will create a db user with this username')
->rules(fn (Get $get) => CreateSite::rules($this->server, $get())['database']),
TextInput::make('database_password')
->rules(fn (Get $get) => CreateSite::rules($this->server, $get())['database']),
]);
}
} }

View File

@ -24,7 +24,7 @@ public function getSecondSubNavigation(): array
if ($user->can('view', [$this->site, $this->server])) { if ($user->can('view', [$this->site, $this->server])) {
$items[] = NavigationItem::make(View::getNavigationLabel()) $items[] = NavigationItem::make(View::getNavigationLabel())
->icon('heroicon-o-globe-alt') ->icon('icon-'.$this->site->type)
->isActiveWhen(fn () => request()->routeIs(View::getRouteName())) ->isActiveWhen(fn () => request()->routeIs(View::getRouteName()))
->url(View::getUrl(parameters: [ ->url(View::getUrl(parameters: [
'server' => $this->server, 'server' => $this->server,

View File

@ -54,15 +54,6 @@ public function getWidgets(): array
if ($this->site->isInstalling()) { if ($this->site->isInstalling()) {
$widgets[] = [Widgets\Installing::class, ['site' => $this->site]]; $widgets[] = [Widgets\Installing::class, ['site' => $this->site]];
if (auth()->user()->can('viewAny', [ServerLog::class, $this->server])) {
$widgets[] = [
LogsList::class, [
'server' => $this->server,
'site' => $this->site,
'label' => 'Logs',
],
];
}
} }
if ($this->site->isReady()) { if ($this->site->isReady()) {
@ -71,6 +62,16 @@ public function getWidgets(): array
} }
} }
if (auth()->user()->can('viewAny', [ServerLog::class, $this->server])) {
$widgets[] = [
LogsList::class, [
'server' => $this->server,
'site' => $this->site,
'label' => 'Logs',
],
];
}
return $widgets; return $widgets;
} }
@ -96,7 +97,9 @@ public function getHeaderActions(): array
$actionsGroup[] = $this->dotEnvAction(); $actionsGroup[] = $this->dotEnvAction();
} }
$actionsGroup[] = $this->branchAction(); if ($this->site->sourceControl) {
$actionsGroup[] = $this->branchAction();
}
$actions[] = ActionGroup::make($actionsGroup) $actions[] = ActionGroup::make($actionsGroup)
->button() ->button()

View File

@ -56,6 +56,7 @@ public function infolist(Infolist $infolist): Infolist
->inlineLabel(), ->inlineLabel(),
TextEntry::make('type') TextEntry::make('type')
->extraAttributes(['class' => 'capitalize']) ->extraAttributes(['class' => 'capitalize'])
->icon(fn ($state) => 'icon-'.$state)
->inlineLabel(), ->inlineLabel(),
TextEntry::make('tags.*') TextEntry::make('tags.*')
->default('No tags') ->default('No tags')
@ -133,6 +134,7 @@ public function infolist(Infolist $infolist): Infolist
), ),
TextEntry::make('source_control_id') TextEntry::make('source_control_id')
->label('Source Control') ->label('Source Control')
->visible(fn (Site $record) => $record->sourceControl)
->formatStateUsing(fn (Site $record) => $record->sourceControl?->profile) ->formatStateUsing(fn (Site $record) => $record->sourceControl?->profile)
->inlineLabel() ->inlineLabel()
->suffixAction( ->suffixAction(

View File

@ -50,7 +50,7 @@ public function infolist(Infolist $infolist): Infolist
TextEntry::make('last_updated_check') TextEntry::make('last_updated_check')
->label('Last Updated Check') ->label('Last Updated Check')
->inlineLabel() ->inlineLabel()
->state(fn (Server $record) => $record->last_update_check?->ago()) ->state(fn (Server $record) => $record->last_update_check ? $record->last_update_check->ago() : '-')
->suffixAction( ->suffixAction(
Action::make('check-update') Action::make('check-update')
->icon('heroicon-o-arrow-path') ->icon('heroicon-o-arrow-path')
@ -63,11 +63,11 @@ public function infolist(Infolist $infolist): Infolist
Notification::make() Notification::make()
->info() ->info()
->title('Available updates:') ->title('Available updates:')
->body($record->available_updates) ->body($record->updates)
->send(); ->send();
}) })
), ),
TextEntry::make('available_updates') TextEntry::make('updates')
->label('Available Updates') ->label('Available Updates')
->inlineLabel() ->inlineLabel()
->suffixAction( ->suffixAction(

View File

@ -32,6 +32,7 @@ class ServerSummary extends Widget implements HasForms, HasInfolists
public function infolist(Infolist $infolist): Infolist public function infolist(Infolist $infolist): Infolist
{ {
return $infolist return $infolist
->name('server-summary')
->schema([ ->schema([
Fieldset::make('info') Fieldset::make('info')
->label('Server Summary') ->label('Server Summary')

View File

@ -5,6 +5,8 @@
use App\Actions\ServerProvider\DeleteServerProvider; use App\Actions\ServerProvider\DeleteServerProvider;
use App\Models\ServerProvider; use App\Models\ServerProvider;
use App\Web\Pages\Settings\ServerProviders\Actions\Edit; use App\Web\Pages\Settings\ServerProviders\Actions\Edit;
use Exception;
use Filament\Notifications\Notification;
use Filament\Support\Enums\MaxWidth; use Filament\Support\Enums\MaxWidth;
use Filament\Tables\Actions\DeleteAction; use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\EditAction; use Filament\Tables\Actions\EditAction;
@ -70,7 +72,14 @@ public function getTable(): Table
->modalHeading('Delete Server Provider') ->modalHeading('Delete Server Provider')
->authorize(fn (ServerProvider $record) => auth()->user()->can('delete', $record)) ->authorize(fn (ServerProvider $record) => auth()->user()->can('delete', $record))
->using(function (array $data, ServerProvider $record) { ->using(function (array $data, ServerProvider $record) {
app(DeleteServerProvider::class)->delete($record); try {
app(DeleteServerProvider::class)->delete($record);
} catch (Exception $e) {
Notification::make()
->danger()
->title($e->getMessage())
->send();
}
}), }),
]); ]);
} }

View File

@ -5,8 +5,10 @@
use App\Actions\SourceControl\DeleteSourceControl; use App\Actions\SourceControl\DeleteSourceControl;
use App\Models\SourceControl; use App\Models\SourceControl;
use App\Web\Pages\Settings\SourceControls\Actions\Edit; use App\Web\Pages\Settings\SourceControls\Actions\Edit;
use Exception;
use Filament\Notifications\Notification;
use Filament\Support\Enums\MaxWidth; use Filament\Support\Enums\MaxWidth;
use Filament\Tables\Actions\DeleteAction; use Filament\Tables\Actions\Action;
use Filament\Tables\Actions\EditAction; use Filament\Tables\Actions\EditAction;
use Filament\Tables\Columns\IconColumn; use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
@ -71,14 +73,24 @@ public function getTable(): Table
->authorize(fn (SourceControl $record) => auth()->user()->can('update', $record)) ->authorize(fn (SourceControl $record) => auth()->user()->can('update', $record))
->using(fn (array $data, SourceControl $record) => Edit::action($record, $data)) ->using(fn (array $data, SourceControl $record) => Edit::action($record, $data))
->modalWidth(MaxWidth::Medium), ->modalWidth(MaxWidth::Medium),
DeleteAction::make('delete') Action::make('delete')
->label('Delete') ->label('Delete')
->icon('heroicon-o-trash')
->color('danger')
->requiresConfirmation()
->modalHeading('Delete Source Control') ->modalHeading('Delete Source Control')
->authorize(fn (SourceControl $record) => auth()->user()->can('delete', $record)) ->authorize(fn (SourceControl $record) => auth()->user()->can('delete', $record))
->using(function (array $data, SourceControl $record) { ->action(function (array $data, SourceControl $record) {
app(DeleteSourceControl::class)->delete($record); try {
app(DeleteSourceControl::class)->delete($record);
$this->dispatch('$refresh'); $this->dispatch('$refresh');
} catch (Exception $e) {
Notification::make()
->danger()
->title($e->getMessage())
->send();
}
}), }),
]); ]);
} }

View File

@ -4,7 +4,7 @@
use App\Enums\StorageProvider; use App\Enums\StorageProvider;
use App\Web\Components\Page; use App\Web\Components\Page;
use Filament\Actions\CreateAction; use Filament\Actions\Action;
use Filament\Support\Enums\MaxWidth; use Filament\Support\Enums\MaxWidth;
class Index extends Page class Index extends Page
@ -34,16 +34,19 @@ public function getWidgets(): array
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
CreateAction::make() Action::make('connect')
->label('Connect') ->label('Connect')
->icon('heroicon-o-wifi') ->icon('heroicon-o-wifi')
->modalHeading('Connect to a Storage Provider') ->modalHeading('Connect to a Storage Provider')
->modalSubmitActionLabel('Connect') ->modalSubmitActionLabel('Connect')
->createAnother(false)
->form(Actions\Create::form()) ->form(Actions\Create::form())
->authorize('create', StorageProvider::class) ->authorize('create', StorageProvider::class)
->modalWidth(MaxWidth::ExtraLarge) ->modalWidth(MaxWidth::ExtraLarge)
->using(fn (array $data) => Actions\Create::action($data)), ->action(function (array $data) {
Actions\Create::action($data);
$this->dispatch('$refresh');
}),
]; ];
} }
} }

View File

@ -5,6 +5,7 @@
use App\Actions\StorageProvider\DeleteStorageProvider; use App\Actions\StorageProvider\DeleteStorageProvider;
use App\Models\StorageProvider; use App\Models\StorageProvider;
use App\Web\Pages\Settings\StorageProviders\Actions\Edit; use App\Web\Pages\Settings\StorageProviders\Actions\Edit;
use Filament\Notifications\Notification;
use Filament\Support\Enums\MaxWidth; use Filament\Support\Enums\MaxWidth;
use Filament\Tables\Actions\DeleteAction; use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\EditAction; use Filament\Tables\Actions\EditAction;
@ -71,7 +72,14 @@ public function getTable(): Table
->modalHeading('Delete Storage Provider') ->modalHeading('Delete Storage Provider')
->authorize(fn (StorageProvider $record) => auth()->user()->can('delete', $record)) ->authorize(fn (StorageProvider $record) => auth()->user()->can('delete', $record))
->using(function (array $data, StorageProvider $record) { ->using(function (array $data, StorageProvider $record) {
app(DeleteStorageProvider::class)->delete($record); try {
app(DeleteStorageProvider::class)->delete($record);
} catch (\Exception $e) {
Notification::make()
->danger()
->title($e->getMessage())
->send();
}
}), }),
]); ]);
} }

View File

@ -14,6 +14,8 @@ public function definition(): array
{ {
return [ return [
'access_token' => Str::random(10), 'access_token' => Str::random(10),
'profile' => $this->faker->name,
'project_id' => null,
]; ];
} }

2593
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,12 @@
{
"resources/css/filament/app/theme.css": {
"file": "assets/theme-5ee727d8.css",
"isEntry": true,
"src": "resources/css/filament/app/theme.css"
},
"resources/js/app.js": {
"file": "assets/app-58d76f18.js",
"isEntry": true,
"src": "resources/js/app.js"
}
}

View File

@ -1,43 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!--simple-icons-->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="3890" height="2168" id="svg2"> <svg class="w-64 h-64" xmlns="http://www.w3.org/2000/svg" role="img" viewBox="0 0 24 24"
<metadata id="metadata8"> fill="#d97706">
<rdf:RDF> <title>phpMyAdmin</title>
<cc:Work rdf:about=""> <path
<dc:format>image/svg+xml</dc:format> d="M5.463 3.476C6.69 5.225 7.497 7.399 7.68 9.798a12.9 12.9 0 0 1-.672 5.254 4.29 4.29 0 0 1 2.969-1.523c.05-.004.099-.006.148-.008.08-.491.47-3.45-.977-6.68-1.068-2.386-3-3.16-3.685-3.365Zm1.777.037s2.406 1.066 3.326 5.547c.607 2.955.049 4.836-.402 5.773a7.347 7.347 0 0 1 4.506-1.994c.86-.065 1.695.02 2.482.233-.1-.741-.593-3.414-2.732-5.92-3.263-3.823-7.18-3.64-7.18-3.64Zm14.817 9.701-17.92 3.049a2.284 2.284 0 0 1 1.535 2.254 2.31 2.31 0 0 1-.106.61c.055-.027 2.689-1.275 6.342-2.034 3.238-.673 5.723-.36 6.285-.273a6.46 6.46 0 0 1 3.864-3.606zm-6.213 4.078c-2.318 0-4.641.495-6.614 1.166-2.868.976-2.951 1.348-5.55 1.043C1.844 19.286 0 18.386 0 18.386s2.406 1.97 4.914 2.127c1.986.125 3.505-.822 5.315-1.414 2.661-.871 4.511-.97 6.253-.975C19.361 18.116 24 19.353 24 19.353s-2.11-1.044-5.033-1.72a13.885 13.885 0 0 0-3.123-.34Z"></path>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs6"/>
<g id="g5" style="fill:#cccccc">
<path d="m 2889.39,6.3480122 -2.04,-4.07 c -1.01,-1.01 -2.03,-2.04 -4.06,-2.04 l -4.08,1.03 c -2.03,2.03 -3.05,3.05 -3.05,5.08 L 2789.64,1572.695 l 13.24,-2.035 83.46,-1523.70199 99.75,163.88 -1.02,0 c 75.32,221.88 106.87,458.02 94.66,708.41003 l 6.11,9.16 98.73,175.05996 6.1,6.11 c 151.66,133.336 321.63,222.907 509.94,268.707 l 45.8,74.309 6.11,2.031 1.02,-2.031 -866.19,-1416.82599 2.04,-29.52" id="path14"/>
<path d="m 2858.86,559.02801 c 106.87,218.84 145.54,549.62999 117.05,992.39199 l 416.29,-50.894 z" id="path18"/>
<path d="m 3807.48,1520.881 c 42.74,-5.086 70.22,-14.246 82.44,-27.476 l -2178.16,265.656 -1.02,0 c 1.02,90.586 41.73,161.836 121.12,212.723 21.37,15.269 43.77,26.465 64.12,33.59 19.34,-22.387 40.72,-38.676 66.17,-53.946 l 1.01,0 c 228,-138.422 566.94,-159.8 1014.78,-65.136 l 5.09,1.011 c 48.86,10.18 97.71,22.399 143.52,36.645 13.22,2.035 24.42,-2.035 33.58,-10.18 16.29,-12.215 36.65,-21.375 64.13,-27.476 l 0,-1.02 c 72.27,-128.25 169.97,-222.906 292.12,-284.996 84.48,-41.727 182.19,-69.215 291.1,-79.395" id="path20"/>
<path d="M 2761.15,1560.585 2860.89,31.798012 C 2716.36,589.56801 2484.29,1110.698 2165.71,1594.167 l 595.44,-33.582" id="path22"/>
<path d="m 2200.32,329.008 -4.07,-2.04 -4.07,1.02 -2.04,5.08 -82.44,1323.18799 12.21,-1.019 82.45,-1322.169 -2.04,-4.06" id="path24"/>
<path d="m 2084.29,1627.756 0,0 87.53,-1290.608 -461.08,1330.308 373.55,-39.7" id="path26"/>
</g>
<g id="g13" style="fill:none;stroke:#cccccc;stroke-width:12;stroke-linecap:round;stroke-linejoin:round">
<path d="m 2102.61,2057.9534 c -115.02,54.9606 -198.48,64.1207 -251.41,30.5387 -52.93,-35.6247 -134.36,-27.4847 -244.28,24.4219" id="path34"/>
<path d="m 2372.33,2066.093 c -21.37,-7.125 -44.78,-12.211 -69.21,-14.25 -49.87,-4.066 -109.93,15.27 -180.16,57.0042 -70.23,42.746 -154.71,59.0351 -255.48,49.8711" id="path36"/>
<path d="m 3000.34,1962.273 c -484.5,-99.742 -833.61,-76.336 -1047.35,71.25" id="path38"/>
<path d="m 3305.69,2010.117 c -21.38,-8.145 -44.79,-13.235 -69.22,-14.254 -49.87,-4.067 -109.92,15.269 -180.15,57.004 -70.23,41.7262 -154.71,58.0114 -255.48,49.8708" id="path40"/>
<path d="m 2655.29,341.21801 c -49.88,-30.54 -101.78,-22.39 -155.73,25.44 -46.82,-47.83 -95.67,-55.98 -146.57,-25.44" id="path42" style="stroke-width:29"/>
<path d="m 2542.31,170.21801 c -50.89,-30.53 -100.76,-18.32 -151.66,36.64 -49.88,-54.96 -100.76,-67.17 -151.66,-36.64" id="path44" style="stroke-width:29"/>
</g>
<g style="fill:#6c78af" id="g33">
<path d="m 56.770404,1763.8603 134.914516,0 c 40.53785,0 70.3051,11.5406 89.30315,34.6149 18.99058,23.0812 25.15303,55.2422 18.48477,96.4878 -2.73388,16.9207 -7.61168,32.4501 -14.63698,46.5933 -7.02825,14.1442 -16.30891,27.067 -27.84598,38.7573 -13.71822,14.0684 -29.07355,24.148 -46.07783,30.2307 -17.00335,6.0818 -38.74881,9.1232 -65.23042,9.1232 l -60.093245,0 -14.951878,92.5154 -70.16450702,0 56.29840402,-348.3226 z m 61.252276,55.1047 -23.613063,146.1033 42.675303,0 c 28.28742,0 49.25184,-5.7744 62.90101,-17.3383 13.63797,-11.5649 22.6337,-30.807 26.98842,-57.7355 4.19709,-25.968 1.75658,-44.2941 -7.2996,-54.9935 -9.06329,-10.6883 -26.92099,-16.036 -53.57751,-16.036 l -48.07456,0" id="path66"/>
<path d="m 378.24196,1671.3451 69.63516,0 -14.95189,92.5152 61.94982,0 c 38.98987,0 66.01185,7.3901 81.07593,22.1571 15.06891,14.7762 19.95224,38.5804 14.64264,71.4329 l -26.22037,162.2172 -70.68973,0 24.9538,-154.3893 c 2.84147,-17.5516 1.46456,-29.4876 -4.12427,-35.815 -5.58913,-6.3193 -17.28356,-9.4871 -35.06873,-9.4871 l -55.57891,0 -32.27423,199.6914 -69.64729,0 56.29807,-348.3224" id="path68"/>
<path d="m 666.43774,1763.8603 134.9135,0 c 40.54695,0 70.30511,11.5406 89.30319,34.6149 18.99454,23.0812 25.15404,55.2422 18.4898,96.4878 -2.7339,16.9207 -7.61577,32.4501 -14.63297,46.5933 -7.03329,14.1442 -16.32202,27.067 -27.85103,38.7573 -13.72229,14.0684 -29.07657,24.148 -46.08084,30.2307 -17.00032,6.0818 -38.74478,9.1232 -65.23548,9.1232 l -60.08435,0 -14.95188,92.5154 -70.16093,0 56.29099,-348.3226 z m 61.26036,55.1047 -23.61306,146.1033 42.6753,0 c 28.28641,0 49.2478,-5.7744 62.88888,-17.3383 13.641,-11.5649 22.64179,-30.807 26.99549,-57.7355 4.19406,-25.968 1.7576,-44.2941 -7.30364,-54.9935 -9.06329,-10.6883 -26.92099,-16.036 -53.56841,-16.036 l -48.07456,0" id="path70"/>
</g>
<g style="fill:#f89c0e" id="g65">
<path d="m 1027.7488,1597.3243 134.9473,0 59.2813,323.8687 163.9729,-323.8687 134.4328,0 -68.203,422.055 -90.0955,0 60.5624,-328.7447 -165.335,328.7447 -97.63,0 -62.0922,-332.1897 -44.4691,332.1897 -93.58345,0 68.21155,-422.055" id="path72"/>
<path d="m 1682.3349,1951.0787 68.129,0 39.0484,-241.6556 84.6928,0 -48.7474,301.634 c -6.6176,41.0085 -21.4426,71.3829 -44.4508,91.1237 -23.0089,19.7312 -54.9295,29.6038 -95.7507,29.6038 l -166.0776,0 10.1515,-62.7878 151.4988,0 c 16.2574,0 29.3646,-3.7991 39.3223,-11.39 9.9443,-7.6043 16.0994,-18.6108 18.427,-33.0342 l 0.8443,-5.1933 -74.9653,0 c -47.9513,0 -80.6531,-9.1406 -98.0934,-27.4219 -17.4525,-18.2801 -22.7167,-48.7649 -15.8297,-91.453 l 30.891,-191.0813 83.7372,0 -29.8633,184.7645 c -3.7745,23.3729 -2.642,38.6968 3.4343,45.9753 6.0641,7.2784 20.5937,10.9158 43.6016,10.9158" id="path74"/>
<path d="m 2102.3044,1597.3243 97.5197,0 118.1874,422.055 -102.2734,0 -23.907,-100.4601 -189.9554,0 -55.1868,100.4601 -97.5321,0 253.1476,-422.055 z m 31.6507,83.8058 -90.1808,162.444 130.2183,0 -40.0375,-162.444" id="path76"/>
<path d="m 2637.3031,2019.3793 -162.6964,0 c -49.1642,0 -85.2663,-13.9786 -108.3193,-41.9409 -23.0404,-27.9573 -30.5053,-66.9321 -22.4309,-116.9135 3.316,-20.4913 9.2287,-39.3092 17.7509,-56.4463 8.5217,-17.1419 19.771,-32.7965 33.7592,-46.9712 16.6286,-17.0415 35.3095,-29.2498 56.0305,-36.6239 20.7209,-7.3752 47.0449,-11.0604 78.9594,-11.0604 l 72.6621,0 18.1132,-112.0988 84.3865,0 -68.2152,422.055 z m -73.6376,-66.4629 28.5697,-176.7241 -51.2224,0 c -34.3524,0 -59.8397,6.9489 -76.4874,20.8321 -16.6223,13.8929 -27.5723,37.0269 -32.8017,69.397 -5.0795,31.4221 -2.0777,53.6898 9.0062,66.8122 11.0837,13.1222 32.7053,19.6828 64.8648,19.6828 l 58.0708,0" id="path78"/>
<path d="m 2761.0228,1709.4231 325.2334,0 c 47.155,0 79.7016,8.9067 97.6259,26.6992 17.9129,17.8022 23.6511,46.7028 17.1903,86.702 l -31.7718,196.555 -85.3299,0 30.0434,-185.9259 c 3.6903,-22.7947 2.5585,-37.8305 -3.3836,-45.1151 -5.966,-7.2785 -20.0182,-10.9208 -42.1807,-10.9208 l -47.5103,0 -39.1073,241.9618 -86.604,0 39.1073,-241.9618 -98.9777,0 -39.1073,241.9618 -85.3298,0 50.1021,-309.9562" id="path80"/>
<path d="m 3389.6515,1674.2008 -88.8214,0 12.4228,-76.8765 88.8214,0 -12.4228,76.8765 z m -55.7924,345.1785 -88.8215,0 50.0899,-309.9562 88.8214,0 -50.0898,309.9562" id="path82"/>
<path d="m 3457.2112,1709.4231 159.3518,0 c 48.1596,0 81.1348,8.7634 98.9115,26.2691 17.7775,17.5156 23.3935,46.5596 16.8359,87.1321 l -31.7597,196.555 -84.705,0 30.1524,-186.5005 c 3.7421,-23.1817 2.4511,-38.2189 -3.8732,-45.1102 -6.325,-6.9 -20.6913,-10.3511 -43.0866,-10.3511 l -68.1168,0 -39.1072,241.9618 -84.6928,0 50.0897,-309.9562" id="path84"/>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,9 +1,9 @@
@props(["value" => 0]) @props(["value" => 0])
<div class="relative flex h-6 overflow-hidden rounded bg-primary-200 text-xs dark:bg-primary-500 dark:bg-opacity-10"> <div class="bg-primary-200 dark:bg-primary-500 relative flex h-6 overflow-hidden rounded text-xs dark:bg-opacity-10">
<div <div
style="width: {{ $value }}%" style="width: {{ $value }}%"
class="flex flex-col justify-center whitespace-nowrap bg-primary-500 text-center text-white shadow-none transition-all duration-500 ease-out" class="bg-primary-500 flex flex-col justify-center whitespace-nowrap text-center text-white shadow-none transition-all duration-500 ease-out"
></div> ></div>
<span <span
class="{{ $value >= 40 ? "text-white" : "text-black dark:text-white" }} absolute left-0 right-0 top-1 text-center font-semibold" class="{{ $value >= 40 ? "text-white" : "text-black dark:text-white" }} absolute left-0 right-0 top-1 text-center font-semibold"

View File

@ -1,10 +1,10 @@
@props([ @props([
'heading' => null, "heading" => null,
'logo' => true, "logo" => true,
'subheading' => null, "subheading" => null,
]) ])
<header class="fi-simple-header flex items-center justify-between h-8"> <header class="fi-simple-header flex h-8 items-center justify-between">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
@if ($logo) @if ($logo)
<x-filament-panels::logo /> <x-filament-panels::logo />
@ -20,9 +20,7 @@ class="fi-simple-header-heading text-center text-2xl font-bold tracking-tight te
</div> </div>
@if (filled($subheading)) @if (filled($subheading))
<p <p class="fi-simple-header-subheading text-center text-sm text-gray-500 dark:text-gray-400">
class="fi-simple-header-subheading text-center text-sm text-gray-500 dark:text-gray-400"
>
{{ $subheading }} {{ $subheading }}
</p> </p>
@endif @endif

View File

@ -5,7 +5,10 @@
use App\Enums\QueueStatus; use App\Enums\QueueStatus;
use App\Facades\SSH; use App\Facades\SSH;
use App\Models\Queue; use App\Models\Queue;
use App\Web\Pages\Servers\Sites\Pages\Queues\Index;
use App\Web\Pages\Servers\Sites\Pages\Queues\Widgets\QueuesList;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Tests\TestCase; use Tests\TestCase;
class QueuesTest extends TestCase class QueuesTest extends TestCase
@ -22,7 +25,7 @@ public function test_see_queues()
]); ]);
$this->get( $this->get(
route('servers.sites.queues', [ Index::getUrl([
'server' => $this->server, 'server' => $this->server,
'site' => $this->site, 'site' => $this->site,
]) ])
@ -42,13 +45,12 @@ public function test_delete_queue()
'site_id' => $this->site->id, 'site_id' => $this->site->id,
]); ]);
$this->delete( Livewire::test(QueuesList::class, [
route('servers.sites.queues.destroy', [ 'server' => $this->server,
'server' => $this->server, 'site' => $this->site,
'site' => $this->site, ])
'queue' => $queue, ->callTableAction('delete', $queue->id)
]) ->assertSuccessful();
)->assertRedirect();
$this->assertDatabaseMissing('queues', [ $this->assertDatabaseMissing('queues', [
'id' => $queue->id, 'id' => $queue->id,
@ -61,19 +63,18 @@ public function test_create_queue()
$this->actingAs($this->user); $this->actingAs($this->user);
$this->post( Livewire::test(Index::class, [
route('servers.sites.queues.store', [ 'server' => $this->server,
'server' => $this->server, 'site' => $this->site,
'site' => $this->site, ])
]), ->callAction('create', [
[
'command' => 'php artisan queue:work', 'command' => 'php artisan queue:work',
'user' => 'vito', 'user' => 'vito',
'auto_start' => 1, 'auto_start' => 1,
'auto_restart' => 1, 'auto_restart' => 1,
'numprocs' => 1, 'numprocs' => 1,
] ])
)->assertSessionDoesntHaveErrors(); ->assertSuccessful();
$this->assertDatabaseHas('queues', [ $this->assertDatabaseHas('queues', [
'server_id' => $this->server->id, 'server_id' => $this->server->id,
@ -99,14 +100,12 @@ public function test_start_queue(): void
'status' => QueueStatus::STOPPED, 'status' => QueueStatus::STOPPED,
]); ]);
$this->post( Livewire::test(QueuesList::class, [
route('servers.sites.queues.action', [ 'server' => $this->server,
'action' => 'start', 'site' => $this->site,
'server' => $this->server, ])
'site' => $this->site, ->callTableAction('start', $queue->id)
'queue' => $queue, ->assertSuccessful();
])
)->assertSessionDoesntHaveErrors();
$this->assertDatabaseHas('queues', [ $this->assertDatabaseHas('queues', [
'id' => $queue->id, 'id' => $queue->id,
@ -126,14 +125,12 @@ public function test_stop_queue(): void
'status' => QueueStatus::RUNNING, 'status' => QueueStatus::RUNNING,
]); ]);
$this->post( Livewire::test(QueuesList::class, [
route('servers.sites.queues.action', [ 'server' => $this->server,
'action' => 'stop', 'site' => $this->site,
'server' => $this->server, ])
'site' => $this->site, ->callTableAction('stop', $queue->id)
'queue' => $queue, ->assertSuccessful();
])
)->assertSessionDoesntHaveErrors();
$this->assertDatabaseHas('queues', [ $this->assertDatabaseHas('queues', [
'id' => $queue->id, 'id' => $queue->id,
@ -153,14 +150,12 @@ public function test_restart_queue(): void
'status' => QueueStatus::RUNNING, 'status' => QueueStatus::RUNNING,
]); ]);
$this->post( Livewire::test(QueuesList::class, [
route('servers.sites.queues.action', [ 'server' => $this->server,
'action' => 'restart', 'site' => $this->site,
'server' => $this->server, ])
'site' => $this->site, ->callTableAction('restart', $queue->id)
'queue' => $queue, ->assertSuccessful();
])
)->assertSessionDoesntHaveErrors();
$this->assertDatabaseHas('queues', [ $this->assertDatabaseHas('queues', [
'id' => $queue->id, 'id' => $queue->id,
@ -180,14 +175,11 @@ public function test_show_logs(): void
'status' => QueueStatus::RUNNING, 'status' => QueueStatus::RUNNING,
]); ]);
$this->get( Livewire::test(QueuesList::class, [
route('servers.sites.queues.logs', [ 'server' => $this->server,
'server' => $this->server, 'site' => $this->site,
'site' => $this->site, ])
'queue' => $queue, ->callTableAction('logs', $queue->id)
]) ->assertSuccessful();
)
->assertSessionDoesntHaveErrors()
->assertSessionHas('content', 'logs');
} }
} }

View File

@ -6,7 +6,12 @@
use App\Facades\SSH; use App\Facades\SSH;
use App\Models\Script; use App\Models\Script;
use App\Models\ScriptExecution; use App\Models\ScriptExecution;
use App\Web\Pages\Scripts\Executions;
use App\Web\Pages\Scripts\Index;
use App\Web\Pages\Scripts\Widgets\ScriptExecutionsList;
use App\Web\Pages\Scripts\Widgets\ScriptsList;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Tests\TestCase; use Tests\TestCase;
class ScriptTest extends TestCase class ScriptTest extends TestCase
@ -21,9 +26,7 @@ public function test_see_scripts(): void
'user_id' => $this->user->id, 'user_id' => $this->user->id,
]); ]);
$this->get( $this->get(Index::getUrl())
route('scripts.index')
)
->assertSuccessful() ->assertSuccessful()
->assertSee($script->name); ->assertSee($script->name);
} }
@ -32,15 +35,12 @@ public function test_create_script(): void
{ {
$this->actingAs($this->user); $this->actingAs($this->user);
$this->post( Livewire::test(Index::class)
route('scripts.store'), ->callAction('create', [
[
'name' => 'Test Script', 'name' => 'Test Script',
'content' => 'echo "Hello, World!"', 'content' => 'echo "Hello, World!"',
] ])
) ->assertSuccessful();
->assertSessionDoesntHaveErrors()
->assertHeader('HX-Redirect');
$this->assertDatabaseHas('scripts', [ $this->assertDatabaseHas('scripts', [
'name' => 'Test Script', 'name' => 'Test Script',
@ -56,12 +56,12 @@ public function test_edit_script(): void
'user_id' => $this->user->id, 'user_id' => $this->user->id,
]); ]);
$this->post(route('scripts.edit', ['script' => $script]), [ Livewire::test(ScriptsList::class)
'name' => 'New Name', ->callTableAction('edit', $script->id, [
'content' => 'echo "Hello, new World!"', 'name' => 'New Name',
]) 'content' => 'echo "Hello, new World!"',
->assertSuccessful() ])
->assertHeader('HX-Redirect'); ->assertSuccessful();
$this->assertDatabaseHas('scripts', [ $this->assertDatabaseHas('scripts', [
'id' => $script->id, 'id' => $script->id,
@ -83,11 +83,9 @@ public function test_delete_script(): void
'status' => ScriptExecutionStatus::EXECUTING, 'status' => ScriptExecutionStatus::EXECUTING,
]); ]);
$this->delete( Livewire::test(ScriptsList::class)
route('scripts.delete', [ ->callTableAction('delete', $script->id)
'script' => $script, ->assertSuccessful();
])
)->assertRedirect();
$this->assertDatabaseMissing('scripts', [ $this->assertDatabaseMissing('scripts', [
'id' => $script->id, 'id' => $script->id,
@ -108,17 +106,14 @@ public function test_execute_script_and_view_log(): void
'user_id' => $this->user->id, 'user_id' => $this->user->id,
]); ]);
$this->post( Livewire::test(Executions::class, [
route('scripts.execute', [ 'script' => $script,
'script' => $script, ])
]), ->callAction('execute', [
[
'server' => $this->server->id, 'server' => $this->server->id,
'user' => 'root', 'user' => 'root',
] ])
) ->assertSuccessful();
->assertSessionDoesntHaveErrors()
->assertHeader('HX-Redirect');
$this->assertDatabaseHas('script_executions', [ $this->assertDatabaseHas('script_executions', [
'script_id' => $script->id, 'script_id' => $script->id,
@ -131,14 +126,11 @@ public function test_execute_script_and_view_log(): void
$execution = $script->lastExecution; $execution = $script->lastExecution;
$this->get( Livewire::test(ScriptExecutionsList::class, [
route('scripts.log', [ 'script' => $script,
'script' => $script, ])
'execution' => $execution, ->callTableAction('logs', $execution->id)
]) ->assertSuccessful();
)
->assertRedirect()
->assertSessionHas('content', 'script output');
} }
public function test_see_executions(): void public function test_see_executions(): void
@ -154,11 +146,7 @@ public function test_see_executions(): void
'status' => ScriptExecutionStatus::EXECUTING, 'status' => ScriptExecutionStatus::EXECUTING,
]); ]);
$this->get( $this->get(Executions::getUrl(['script' => $script]))
route('scripts.show', [
'script' => $script,
])
)
->assertSuccessful() ->assertSuccessful()
->assertSee($scriptExecution->status); ->assertSee($scriptExecution->status);
} }

View File

@ -1,58 +0,0 @@
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class SearchTest extends TestCase
{
use RefreshDatabase;
public function test_search_server(): void
{
$this->actingAs($this->user);
$this->get(route('search', ['q' => $this->server->name]))
->assertOk()
->assertJson([
'results' => [
[
'type' => 'server',
'url' => route('servers.show', ['server' => $this->site->server]),
'text' => $this->server->name,
'project' => $this->server->project->name,
],
],
]);
}
public function test_search_site(): void
{
$this->actingAs($this->user);
$this->get(route('search', ['q' => $this->site->domain]))
->assertOk()
->assertJson([
'results' => [
[
'type' => 'site',
'url' => route('servers.sites.show', ['server' => $this->site->server, 'site' => $this->site]),
'text' => $this->site->domain,
'project' => $this->site->server->project->name,
],
],
]);
}
public function test_search_has_no_results(): void
{
$this->actingAs($this->user);
$this->get(route('search', ['q' => 'nothing-will-found']))
->assertOk()
->assertJson([
'results' => [],
]);
}
}

View File

@ -5,7 +5,10 @@
use App\Enums\SshKeyStatus; use App\Enums\SshKeyStatus;
use App\Facades\SSH; use App\Facades\SSH;
use App\Models\SshKey; use App\Models\SshKey;
use App\Web\Pages\Servers\SSHKeys\Index;
use App\Web\Pages\Servers\SSHKeys\Widgets\SshKeysList;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Tests\TestCase; use Tests\TestCase;
class ServerKeysTest extends TestCase class ServerKeysTest extends TestCase
@ -26,7 +29,7 @@ public function test_see_server_keys()
'status' => SshKeyStatus::ADDED, 'status' => SshKeyStatus::ADDED,
]); ]);
$this->get(route('servers.ssh-keys', $this->server)) $this->get(Index::getUrl(['server' => $this->server]))
->assertSuccessful() ->assertSuccessful()
->assertSeeText('My first key'); ->assertSeeText('My first key');
} }
@ -47,7 +50,11 @@ public function test_delete_ssh_key()
'status' => SshKeyStatus::ADDED, 'status' => SshKeyStatus::ADDED,
]); ]);
$this->delete(route('servers.ssh-keys.destroy', [$this->server, $sshKey])); Livewire::test(SshKeysList::class, [
'server' => $this->server,
])
->callTableAction('delete', $sshKey->id)
->assertSuccessful();
$this->assertDatabaseMissing('server_ssh_keys', [ $this->assertDatabaseMissing('server_ssh_keys', [
'server_id' => $this->server->id, 'server_id' => $this->server->id,
@ -61,10 +68,15 @@ public function test_add_new_ssh_key()
$this->actingAs($this->user); $this->actingAs($this->user);
$this->post(route('servers.ssh-keys.store', $this->server), [ Livewire::test(Index::class, [
'name' => 'My first key', 'server' => $this->server,
'public_key' => 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC3CCnyBbpCgOJ0AWUSfBZ+mYAsYzcQDegPkBx1kyE0bXT1yX4+6uYx1Jh6NxWgLyaU0BaP4nsClrK1u5FojQHd8J7ycc0N3H8B+v2NPzj1Q6bFnl40saastONVm+d4edbCg9BowGAafLcf9ALsognqqOWQbK/QOpAhg25IAe47eiY3IjDGMHlsvaZkMtkDhT4t1mK8ZLjxw5vjyVYgINJefR981bIxMFrXy+0xBCsYOZxMIoAJsgCkrAGlI4kQHKv0SQVccSyTE1eziIZa5b3QUlXj8ogxMfK/EOD7Aoqinw652k4S5CwFs/LLmjWcFqCKDM6CSggWpB78DZ729O6zFvQS9V99/9SsSV7Qc5ML7B0DKzJ/tbHkaAE8xdZnQnZFVUegUMtUmjvngMaGlYsxkAZrUKsFRoh7xfXVkDyRBaBSslRNe8LFsXw9f7Q+3jdZ5vhGhmp+TBXTlgxApwR023411+ABE9y0doCx8illya3m2olEiiMZkRclgqsWFSk=', ])
]); ->callAction('deploy', [
'type' => 'new',
'name' => 'My first key',
'public_key' => 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC3CCnyBbpCgOJ0AWUSfBZ+mYAsYzcQDegPkBx1kyE0bXT1yX4+6uYx1Jh6NxWgLyaU0BaP4nsClrK1u5FojQHd8J7ycc0N3H8B+v2NPzj1Q6bFnl40saastONVm+d4edbCg9BowGAafLcf9ALsognqqOWQbK/QOpAhg25IAe47eiY3IjDGMHlsvaZkMtkDhT4t1mK8ZLjxw5vjyVYgINJefR981bIxMFrXy+0xBCsYOZxMIoAJsgCkrAGlI4kQHKv0SQVccSyTE1eziIZa5b3QUlXj8ogxMfK/EOD7Aoqinw652k4S5CwFs/LLmjWcFqCKDM6CSggWpB78DZ729O6zFvQS9V99/9SsSV7Qc5ML7B0DKzJ/tbHkaAE8xdZnQnZFVUegUMtUmjvngMaGlYsxkAZrUKsFRoh7xfXVkDyRBaBSslRNe8LFsXw9f7Q+3jdZ5vhGhmp+TBXTlgxApwR023411+ABE9y0doCx8illya3m2olEiiMZkRclgqsWFSk=',
])
->assertSuccessful();
$this->assertDatabaseHas('server_ssh_keys', [ $this->assertDatabaseHas('server_ssh_keys', [
'server_id' => $this->server->id, 'server_id' => $this->server->id,
@ -84,9 +96,14 @@ public function test_add_existing_key()
'public_key' => 'public-key-content', 'public_key' => 'public-key-content',
]); ]);
$this->post(route('servers.ssh-keys.deploy', $this->server), [ Livewire::test(Index::class, [
'key_id' => $sshKey->id, 'server' => $this->server,
]); ])
->callAction('deploy', [
'type' => 'existing',
'key_id' => $sshKey->id,
])
->assertSuccessful();
$this->assertDatabaseHas('server_ssh_keys', [ $this->assertDatabaseHas('server_ssh_keys', [
'server_id' => $this->server->id, 'server_id' => $this->server->id,
@ -110,26 +127,35 @@ public function test_create_ssh_key_handles_invalid_or_partial_keys(array $postB
'public_key' => 'public-key-content', 'public_key' => 'public-key-content',
]); ]);
$response = $this->post(route('servers.ssh-keys.store', $this->server), $postBody); $postBody['type'] = 'new';
$response = Livewire::test(Index::class, [
'server' => $this->server,
])
->callAction('deploy', $postBody);
if ($expectedToSucceed) { if ($expectedToSucceed) {
$response->assertSessionDoesntHaveErrors(); $response->assertSuccessful();
$this->assertDatabaseHas('ssh_keys', [
'name' => $postBody['name'],
]);
$this->assertDatabaseHas('server_ssh_keys', [
'server_id' => $this->server->id,
'status' => SshKeyStatus::ADDED,
]);
} else { } else {
$response->assertSessionHasErrors('public_key', 'Invalid key'); $response->assertHasActionErrors([
'public_key',
]);
$this->assertDatabaseMissing('server_ssh_keys', [
'server_id' => $this->server->id,
'status' => SshKeyStatus::ADDED,
]);
} }
} }
public static function ssh_key_data_provider(): array public static function ssh_key_data_provider(): array
{ {
return [ return [
[
[
'name' => 'My first key',
// Key Already exists
'public_key' => 'public-key-content',
],
self::EXPECT_FAILURE,
],
[ [
[ [
'name' => 'My first key', 'name' => 'My first key',

View File

@ -3,8 +3,11 @@
namespace Tests\Feature; namespace Tests\Feature;
use App\Enums\ServerProvider; use App\Enums\ServerProvider;
use App\Web\Pages\Settings\ServerProviders\Index;
use App\Web\Pages\Settings\ServerProviders\Widgets\ServerProvidersList;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Livewire\Livewire;
use Tests\TestCase; use Tests\TestCase;
class ServerProvidersTest extends TestCase class ServerProvidersTest extends TestCase
@ -27,7 +30,10 @@ public function test_connect_provider(string $provider, array $input): void
], ],
$input $input
); );
$this->post(route('settings.server-providers.connect'), $data)->assertSessionDoesntHaveErrors(); Livewire::test(Index::class)
->callAction('create', $data)
->assertHasNoActionErrors()
->assertSuccessful();
$this->assertDatabaseHas('server_providers', [ $this->assertDatabaseHas('server_providers', [
'provider' => $provider, 'provider' => $provider,
@ -54,7 +60,10 @@ public function test_cannot_connect_to_provider(string $provider, array $input):
], ],
$input $input
); );
$this->post(route('settings.server-providers.connect'), $data)->assertSessionHasErrors(); Livewire::test(Index::class)
->callAction('create', $data)
->assertActionHalted('create')
->assertNotified();
$this->assertDatabaseMissing('server_providers', [ $this->assertDatabaseMissing('server_providers', [
'provider' => $provider, 'provider' => $provider,
@ -70,7 +79,7 @@ public function test_see_providers_list(): void
'user_id' => $this->user->id, 'user_id' => $this->user->id,
]); ]);
$this->get(route('settings.server-providers')) $this->get(Index::getUrl())
->assertSuccessful() ->assertSuccessful()
->assertSee($provider->profile); ->assertSee($provider->profile);
} }
@ -87,8 +96,9 @@ public function test_delete_provider(string $provider): void
'provider' => $provider, 'provider' => $provider,
]); ]);
$this->delete(route('settings.server-providers.delete', $provider)) Livewire::test(ServerProvidersList::class)
->assertSessionDoesntHaveErrors(); ->callTableAction('delete', $provider->id)
->assertSuccessful();
$this->assertDatabaseMissing('server_providers', [ $this->assertDatabaseMissing('server_providers', [
'id' => $provider->id, 'id' => $provider->id,
@ -111,10 +121,9 @@ public function test_cannot_delete_provider(string $provider): void
'provider_id' => $provider->id, 'provider_id' => $provider->id,
]); ]);
$this->delete(route('settings.server-providers.delete', $provider)) Livewire::test(ServerProvidersList::class)
->assertSessionDoesntHaveErrors() ->callTableAction('delete', $provider->id)
->assertSessionHas('toast.type', 'error') ->assertNotified('This server provider is being used by a server.');
->assertSessionHas('toast.message', 'This server provider is being used by a server.');
$this->assertDatabaseHas('server_providers', [ $this->assertDatabaseHas('server_providers', [
'id' => $provider->id, 'id' => $provider->id,

View File

@ -10,10 +10,18 @@
use App\Enums\ServiceStatus; use App\Enums\ServiceStatus;
use App\Enums\Webserver; use App\Enums\Webserver;
use App\Facades\SSH; use App\Facades\SSH;
use App\Models\Server;
use App\NotificationChannels\Email\NotificationMail; use App\NotificationChannels\Email\NotificationMail;
use App\Web\Pages\Servers\Index;
use App\Web\Pages\Servers\Settings;
use App\Web\Pages\Servers\Widgets\ServerDetails;
use App\Web\Pages\Servers\Widgets\ServerSummary;
use App\Web\Pages\Servers\Widgets\UpdateServerInfo;
use Filament\Notifications\Notification;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Livewire\Livewire;
use Tests\TestCase; use Tests\TestCase;
class ServerTest extends TestCase class ServerTest extends TestCase
@ -26,17 +34,19 @@ public function test_create_regular_server(): void
SSH::fake('Active: active'); // fake output for service installations SSH::fake('Active: active'); // fake output for service installations
$this->post(route('servers.create'), [ Livewire::test(Index::class)
'type' => ServerType::REGULAR, ->callAction('create', [
'provider' => ServerProvider::CUSTOM, 'type' => ServerType::REGULAR,
'name' => 'test', 'provider' => ServerProvider::CUSTOM,
'ip' => '1.1.1.1', 'name' => 'test',
'port' => '22', 'ip' => '1.1.1.1',
'os' => OperatingSystem::UBUNTU22, 'port' => '22',
'webserver' => Webserver::NGINX, 'os' => OperatingSystem::UBUNTU22,
'database' => Database::MYSQL80, 'webserver' => Webserver::NGINX,
'php' => '8.2', 'database' => Database::MYSQL80,
])->assertSessionDoesntHaveErrors(); 'php' => '8.2',
])
->assertSuccessful();
$this->assertDatabaseHas('servers', [ $this->assertDatabaseHas('servers', [
'name' => 'test', 'name' => 'test',
@ -82,17 +92,19 @@ public function test_create_database_server(): void
SSH::fake('Active: active'); // fake output for service installations SSH::fake('Active: active'); // fake output for service installations
$this->post(route('servers.create'), [ Livewire::test(Index::class)
'type' => ServerType::DATABASE, ->callAction('create', [
'provider' => ServerProvider::CUSTOM, 'type' => ServerType::DATABASE,
'name' => 'test', 'provider' => ServerProvider::CUSTOM,
'ip' => '2.2.2.2', 'name' => 'test',
'port' => '22', 'ip' => '2.2.2.2',
'os' => OperatingSystem::UBUNTU22, 'port' => '22',
'database' => Database::MYSQL80, 'os' => OperatingSystem::UBUNTU22,
])->assertSessionDoesntHaveErrors(); 'database' => Database::MYSQL80,
])
->assertSuccessful();
$server = \App\Models\Server::query()->where('ip', '2.2.2.2')->first(); $server = Server::query()->where('ip', '2.2.2.2')->first();
$this->assertDatabaseHas('servers', [ $this->assertDatabaseHas('servers', [
'name' => 'test', 'name' => 'test',
@ -138,8 +150,11 @@ public function test_delete_server(): void
SSH::fake(); SSH::fake();
$this->delete(route('servers.delete', $this->server)) Livewire::test(Settings::class, [
->assertSessionDoesntHaveErrors(); 'server' => $this->server,
])->callAction('delete')
->assertSuccessful()
->assertRedirect(Index::getUrl());
$this->assertDatabaseMissing('servers', [ $this->assertDatabaseMissing('servers', [
'id' => $this->server->id, 'id' => $this->server->id,
@ -172,8 +187,11 @@ public function test_cannot_delete_on_provider(): void
], ],
]); ]);
$this->delete(route('servers.delete', $this->server)) Livewire::test(Settings::class, [
->assertSessionDoesntHaveErrors(); 'server' => $this->server,
])->callAction('delete')
->assertSuccessful()
->assertRedirect(Index::getUrl());
$this->assertDatabaseMissing('servers', [ $this->assertDatabaseMissing('servers', [
'id' => $this->server->id, 'id' => $this->server->id,
@ -190,8 +208,12 @@ public function test_check_connection_is_ready(): void
$this->server->update(['status' => ServerStatus::DISCONNECTED]); $this->server->update(['status' => ServerStatus::DISCONNECTED]);
$this->post(route('servers.settings.check-connection', $this->server)) Livewire::test(ServerSummary::class, [
->assertSessionDoesntHaveErrors(); 'server' => $this->server,
])
->callInfolistAction('status', 'check-status')
->assertSuccessful()
->assertNotified('Server is '.ServerStatus::READY);
$this->assertDatabaseHas('servers', [ $this->assertDatabaseHas('servers', [
'id' => $this->server->id, 'id' => $this->server->id,
@ -207,8 +229,12 @@ public function test_connection_failed(): void
$this->server->update(['status' => ServerStatus::READY]); $this->server->update(['status' => ServerStatus::READY]);
$this->post(route('servers.settings.check-connection', $this->server)) Livewire::test(ServerSummary::class, [
->assertSessionDoesntHaveErrors(); 'server' => $this->server,
])
->callInfolistAction('status', 'check-status')
->assertSuccessful()
->assertNotified('Server is '.ServerStatus::DISCONNECTED);
$this->assertDatabaseHas('servers', [ $this->assertDatabaseHas('servers', [
'id' => $this->server->id, 'id' => $this->server->id,
@ -222,8 +248,11 @@ public function test_reboot_server(): void
$this->actingAs($this->user); $this->actingAs($this->user);
$this->post(route('servers.settings.reboot', $this->server)) Livewire::test(Settings::class, [
->assertSessionDoesntHaveErrors(); 'server' => $this->server,
])
->callAction('reboot')
->assertSuccessful();
$this->assertDatabaseHas('servers', [ $this->assertDatabaseHas('servers', [
'id' => $this->server->id, 'id' => $this->server->id,
@ -233,11 +262,20 @@ public function test_reboot_server(): void
public function test_edit_server(): void public function test_edit_server(): void
{ {
SSH::fake();
$this->actingAs($this->user); $this->actingAs($this->user);
$this->post(route('servers.settings.edit', $this->server), [ Livewire::test(UpdateServerInfo::class, [
'name' => 'new-name', 'server' => $this->server,
])->assertSessionDoesntHaveErrors(); ])
->fill([
'name' => 'new-name',
'ip' => $this->server->ip,
'port' => $this->server->port,
])
->call('submit')
->assertSuccessful();
$this->assertDatabaseHas('servers', [ $this->assertDatabaseHas('servers', [
'id' => $this->server->id, 'id' => $this->server->id,
@ -251,9 +289,16 @@ public function test_edit_server_ip_address(): void
$this->actingAs($this->user); $this->actingAs($this->user);
$this->post(route('servers.settings.edit', $this->server), [ Livewire::test(UpdateServerInfo::class, [
'ip' => '2.2.2.2', 'server' => $this->server,
])->assertSessionDoesntHaveErrors(); ])
->fill([
'name' => $this->server->name,
'ip' => '2.2.2.2',
'port' => $this->server->port,
])
->call('submit')
->assertSuccessful();
$this->assertDatabaseHas('servers', [ $this->assertDatabaseHas('servers', [
'id' => $this->server->id, 'id' => $this->server->id,
@ -268,10 +313,16 @@ public function test_edit_server_ip_address_and_disconnect(): void
$this->actingAs($this->user); $this->actingAs($this->user);
$this->post(route('servers.settings.edit', $this->server), [ Livewire::test(UpdateServerInfo::class, [
'ip' => '2.2.2.2', 'server' => $this->server,
'port' => 2222, ])
])->assertSessionDoesntHaveErrors(); ->fill([
'name' => $this->server->name,
'ip' => '2.2.2.2',
'port' => 2222,
])
->call('submit')
->assertSuccessful();
$this->assertDatabaseHas('servers', [ $this->assertDatabaseHas('servers', [
'id' => $this->server->id, 'id' => $this->server->id,
@ -287,8 +338,17 @@ public function test_check_updates(): void
$this->actingAs($this->user); $this->actingAs($this->user);
$this->post(route('servers.settings.check-updates', $this->server)) Livewire::test(ServerDetails::class, [
->assertSessionDoesntHaveErrors(); 'server' => $this->server,
])
->callInfolistAction('last_updated_check', 'check-update')
->assertSuccessful()
->assertNotified(
Notification::make()
->info()
->title('Available updates:')
->body(9)
);
$this->server->refresh(); $this->server->refresh();
$this->assertEquals(9, $this->server->updates); $this->assertEquals(9, $this->server->updates);
@ -300,8 +360,11 @@ public function test_update_server(): void
$this->actingAs($this->user); $this->actingAs($this->user);
$this->post(route('servers.settings.update', $this->server)) Livewire::test(ServerDetails::class, [
->assertSessionDoesntHaveErrors(); 'server' => $this->server,
])
->callInfolistAction('updates', 'update-server')
->assertSuccessful();
$this->server->refresh(); $this->server->refresh();

View File

@ -5,9 +5,12 @@
use App\Enums\ServiceStatus; use App\Enums\ServiceStatus;
use App\Facades\SSH; use App\Facades\SSH;
use App\Models\Server; use App\Models\Server;
use App\Web\Pages\Servers\Services\Index;
use App\Web\Pages\Servers\Services\Widgets\ServicesList;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Livewire\Livewire;
use Tests\TestCase; use Tests\TestCase;
class ServicesTest extends TestCase class ServicesTest extends TestCase
@ -18,14 +21,13 @@ public function test_see_services_list(): void
{ {
$this->actingAs($this->user); $this->actingAs($this->user);
$this->get(route('servers.services', $this->server)) $this->get(Index::getUrl(['server' => $this->server]))
->assertSuccessful() ->assertSuccessful()
->assertSee('mysql') ->assertSee('mysql')
->assertSee('nginx') ->assertSee('nginx')
->assertSee('php') ->assertSee('php')
->assertSee('supervisor') ->assertSee('supervisor')
->assertSee('redis') ->assertSee('redis')
->assertSee('vito-agent')
->assertSee('ufw'); ->assertSee('ufw');
} }
@ -37,13 +39,16 @@ public function test_restart_service(string $name): void
$this->actingAs($this->user); $this->actingAs($this->user);
$service = $this->server->services()->where('name', $name)->firstOrFail(); $service = $this->server->services()->where('name', $name)->firstOrFail();
$service->status = ServiceStatus::STOPPED;
$service->save();
SSH::fake('Active: active'); SSH::fake('Active: active');
$this->get(route('servers.services.restart', [ Livewire::test(ServicesList::class, [
'server' => $this->server, 'server' => $this->server,
'service' => $service, ])
]))->assertSessionDoesntHaveErrors(); ->callTableAction('restart', $service->id)
->assertSuccessful();
$service->refresh(); $service->refresh();
@ -61,10 +66,11 @@ public function test_failed_to_restart_service(string $name): void
SSH::fake('Active: inactive'); SSH::fake('Active: inactive');
$this->get(route('servers.services.restart', [ Livewire::test(ServicesList::class, [
'server' => $this->server, 'server' => $this->server,
'service' => $service, ])
]))->assertSessionDoesntHaveErrors(); ->callTableAction('restart', $service->id)
->assertSuccessful();
$service->refresh(); $service->refresh();
@ -82,10 +88,11 @@ public function test_stop_service(string $name): void
SSH::fake('Active: inactive'); SSH::fake('Active: inactive');
$this->get(route('servers.services.stop', [ Livewire::test(ServicesList::class, [
'server' => $this->server, 'server' => $this->server,
'service' => $service, ])
]))->assertSessionDoesntHaveErrors(); ->callTableAction('stop', $service->id)
->assertSuccessful();
$service->refresh(); $service->refresh();
@ -103,10 +110,11 @@ public function test_failed_to_stop_service(string $name): void
SSH::fake('Active: active'); SSH::fake('Active: active');
$this->get(route('servers.services.stop', [ Livewire::test(ServicesList::class, [
'server' => $this->server, 'server' => $this->server,
'service' => $service, ])
]))->assertSessionDoesntHaveErrors(); ->callTableAction('stop', $service->id)
->assertSuccessful();
$service->refresh(); $service->refresh();
@ -121,13 +129,16 @@ public function test_start_service(string $name): void
$this->actingAs($this->user); $this->actingAs($this->user);
$service = $this->server->services()->where('name', $name)->firstOrFail(); $service = $this->server->services()->where('name', $name)->firstOrFail();
$service->status = ServiceStatus::STOPPED;
$service->save();
SSH::fake('Active: active'); SSH::fake('Active: active');
$this->get(route('servers.services.start', [ Livewire::test(ServicesList::class, [
'server' => $this->server, 'server' => $this->server,
'service' => $service, ])
]))->assertSessionDoesntHaveErrors(); ->callTableAction('start', $service->id)
->assertSuccessful();
$service->refresh(); $service->refresh();
@ -145,10 +156,11 @@ public function test_failed_to_start_service(string $name): void
SSH::fake('Active: inactive'); SSH::fake('Active: inactive');
$this->get(route('servers.services.start', [ Livewire::test(ServicesList::class, [
'server' => $this->server, 'server' => $this->server,
'service' => $service, ])
]))->assertSessionDoesntHaveErrors(); ->callTableAction('start', $service->id)
->assertSuccessful();
$service->refresh(); $service->refresh();
@ -163,13 +175,16 @@ public function test_enable_service(string $name): void
$this->actingAs($this->user); $this->actingAs($this->user);
$service = $this->server->services()->where('name', $name)->firstOrFail(); $service = $this->server->services()->where('name', $name)->firstOrFail();
$service->status = ServiceStatus::DISABLED;
$service->save();
SSH::fake('Active: active'); SSH::fake('Active: active');
$this->get(route('servers.services.enable', [ Livewire::test(ServicesList::class, [
'server' => $this->server, 'server' => $this->server,
'service' => $service, ])
]))->assertSessionDoesntHaveErrors(); ->callTableAction('enable', $service->id)
->assertSuccessful();
$service->refresh(); $service->refresh();
@ -187,10 +202,11 @@ public function test_failed_to_enable_service(string $name): void
SSH::fake('Active: inactive'); SSH::fake('Active: inactive');
$this->get(route('servers.services.enable', [ Livewire::test(ServicesList::class, [
'server' => $this->server, 'server' => $this->server,
'service' => $service, ])
]))->assertSessionDoesntHaveErrors(); ->callTableAction('enable', $service->id)
->assertSuccessful();
$service->refresh(); $service->refresh();
@ -208,10 +224,11 @@ public function test_disable_service(string $name): void
SSH::fake('Active: inactive'); SSH::fake('Active: inactive');
$this->get(route('servers.services.disable', [ Livewire::test(ServicesList::class, [
'server' => $this->server, 'server' => $this->server,
'service' => $service, ])
]))->assertSessionDoesntHaveErrors(); ->callTableAction('disable', $service->id)
->assertSuccessful();
$service->refresh(); $service->refresh();
@ -229,10 +246,11 @@ public function test_failed_to_disable_service(string $name): void
SSH::fake('Active: active'); SSH::fake('Active: active');
$this->get(route('servers.services.disable', [ Livewire::test(ServicesList::class, [
'server' => $this->server, 'server' => $this->server,
'service' => $service, ])
]))->assertSessionDoesntHaveErrors(); ->callTableAction('disable', $service->id)
->assertSuccessful();
$service->refresh(); $service->refresh();
@ -262,13 +280,15 @@ public function test_install_service(string $name, string $type, string $version
if (! File::exists($keys['public_key_path']) || ! File::exists($keys['private_key_path'])) { if (! File::exists($keys['public_key_path']) || ! File::exists($keys['private_key_path'])) {
$server->provider()->generateKeyPair(); $server->provider()->generateKeyPair();
} }
$this->post(route('servers.services.install', [
Livewire::test(Index::class, [
'server' => $server, 'server' => $server,
]), [ ])
'name' => $name, ->callAction('install', [
'type' => $type, 'name' => $name,
'version' => $version, 'version' => $version,
])->assertSessionDoesntHaveErrors(); ])
->assertSuccessful();
$this->assertDatabaseHas('services', [ $this->assertDatabaseHas('services', [
'server_id' => $server->id, 'server_id' => $server->id,

View File

@ -7,8 +7,13 @@
use App\Enums\SourceControl; use App\Enums\SourceControl;
use App\Facades\SSH; use App\Facades\SSH;
use App\Models\Site; use App\Models\Site;
use App\Web\Pages\Servers\Sites\Index;
use App\Web\Pages\Servers\Sites\Settings;
use App\Web\Pages\Servers\Sites\View;
use App\Web\Pages\Servers\Sites\Widgets\SiteDetails;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Livewire\Livewire;
use Tests\TestCase; use Tests\TestCase;
class SitesTest extends TestCase class SitesTest extends TestCase
@ -36,12 +41,15 @@ public function test_create_site(array $inputs): void
$inputs['source_control'] = $sourceControl->id; $inputs['source_control'] = $sourceControl->id;
$this->post(route('servers.sites.create', [ Livewire::test(Index::class, [
'server' => $this->server, 'server' => $this->server,
]), $inputs)->assertSessionDoesntHaveErrors(); ])
->callAction('create', $inputs)
->assertHasNoActionErrors()
->assertSuccessful();
$this->assertDatabaseHas('sites', [ $this->assertDatabaseHas('sites', [
'domain' => 'example.com', 'domain' => $inputs['domain'],
'aliases' => json_encode($inputs['aliases'] ?? []), 'aliases' => json_encode($inputs['aliases'] ?? []),
'status' => SiteStatus::READY, 'status' => SiteStatus::READY,
]); ]);
@ -79,9 +87,12 @@ public function test_create_site_failed_due_to_source_control(int $status): void
$inputs['source_control'] = $sourceControl->id; $inputs['source_control'] = $sourceControl->id;
$this->post(route('servers.sites.create', [ Livewire::test(Index::class, [
'server' => $this->server, 'server' => $this->server,
]), $inputs)->assertSessionHasErrors(); ])
->callAction('create', $inputs)
->assertNotified()
->assertSuccessful();
$this->assertDatabaseMissing('sites', [ $this->assertDatabaseMissing('sites', [
'domain' => 'example.com', 'domain' => 'example.com',
@ -97,9 +108,7 @@ public function test_see_sites_list(): void
'server_id' => $this->server->id, 'server_id' => $this->server->id,
]); ]);
$this->get(route('servers.sites', [ $this->get(Index::getUrl(['server' => $this->server]))
'server' => $this->server,
]))
->assertSuccessful() ->assertSuccessful()
->assertSee($site->domain); ->assertSee($site->domain);
} }
@ -114,10 +123,13 @@ public function test_delete_site(): void
'server_id' => $this->server->id, 'server_id' => $this->server->id,
]); ]);
$this->delete(route('servers.sites.destroy', [ Livewire::test(Settings::class, [
'server' => $this->server, 'server' => $this->server,
'site' => $site, 'site' => $site,
]))->assertRedirect(); ])
->callAction('delete')
->assertHasNoActionErrors()
->assertSuccessful();
$this->assertDatabaseMissing('sites', [ $this->assertDatabaseMissing('sites', [
'id' => $site->id, 'id' => $site->id,
@ -134,12 +146,14 @@ public function test_change_php_version(): void
'server_id' => $this->server->id, 'server_id' => $this->server->id,
]); ]);
$this->post(route('servers.sites.settings.php', [ Livewire::test(SiteDetails::class, [
'server' => $this->server,
'site' => $site, 'site' => $site,
]), [ ])
'version' => '8.2', ->callInfolistAction('php_version', 'edit_php_version', [
])->assertSessionDoesntHaveErrors(); 'version' => '8.2',
])
->assertHasNoActionErrors()
->assertSuccessful();
$site->refresh(); $site->refresh();
@ -162,12 +176,14 @@ public function test_update_source_control(): void
'provider' => SourceControl::GITHUB, 'provider' => SourceControl::GITHUB,
]); ]);
$this->post(route('servers.sites.settings.source-control', [ Livewire::test(SiteDetails::class, [
'server' => $this->server,
'site' => $this->site, 'site' => $this->site,
]), [ ])
'source_control' => $sourceControl->id, ->callInfolistAction('source_control_id', 'edit_source_control', [
])->assertSessionDoesntHaveErrors(); 'source_control' => $sourceControl->id,
])
->assertHasNoActionErrors()
->assertSuccessful();
$this->site->refresh(); $this->site->refresh();
@ -190,12 +206,13 @@ public function test_failed_to_update_source_control(): void
'provider' => SourceControl::GITHUB, 'provider' => SourceControl::GITHUB,
]); ]);
$this->post(route('servers.sites.settings.source-control', [ Livewire::test(SiteDetails::class, [
'server' => $this->server,
'site' => $this->site, 'site' => $this->site,
]), [ ])
'source_control' => $sourceControl->id, ->callInfolistAction('source_control_id', 'edit_source_control', [
])->assertSessionHasErrors(); 'source_control' => $sourceControl->id,
])
->assertHasActionErrors();
} }
public function test_update_v_host(): void public function test_update_v_host(): void
@ -208,10 +225,26 @@ public function test_update_v_host(): void
'server_id' => $this->server->id, 'server_id' => $this->server->id,
]); ]);
$this->get(route('servers.sites.settings.vhost', [ Livewire::test(Settings::class, [
'server' => $this->server, 'server' => $this->server,
'site' => $site, 'site' => $site,
]))->assertSessionDoesntHaveErrors(); ])
->callAction('vhost', [
'vhost' => 'test',
])
->assertNotified('VHost updated!');
}
public function test_see_logs(): void
{
$this->actingAs($this->user);
$this->get(View::getUrl([
'server' => $this->server,
'site' => $this->site,
]))
->assertSuccessful()
->assertSee('Logs');
} }
public static function create_data(): array public static function create_data(): array
@ -273,16 +306,4 @@ public static function create_failure_data(): array
[404], [404],
]; ];
} }
public function test_see_logs(): void
{
$this->actingAs($this->user);
$this->get(route('servers.sites.logs', [
'server' => $this->server,
'site' => $this->site,
]))
->assertSuccessful()
->assertSee('Vito Logs');
}
} }

View File

@ -3,8 +3,11 @@
namespace Tests\Feature; namespace Tests\Feature;
use App\Models\SourceControl; use App\Models\SourceControl;
use App\Web\Pages\Settings\SourceControls\Index;
use App\Web\Pages\Settings\SourceControls\Widgets\SourceControlsList;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Livewire\Livewire;
use Tests\TestCase; use Tests\TestCase;
class SourceControlsTest extends TestCase class SourceControlsTest extends TestCase
@ -28,8 +31,10 @@ public function test_connect_provider(string $provider, ?string $customUrl, arra
if ($customUrl !== null) { if ($customUrl !== null) {
$input['url'] = $customUrl; $input['url'] = $customUrl;
} }
$this->post(route('settings.source-controls.connect'), $input)
->assertSessionDoesntHaveErrors(); Livewire::test(Index::class)
->callAction('connect', $input)
->assertSuccessful();
$this->assertDatabaseHas('source_controls', [ $this->assertDatabaseHas('source_controls', [
'provider' => $provider, 'provider' => $provider,
@ -64,10 +69,11 @@ public function test_delete_provider(string $provider): void
'profile' => 'test', 'profile' => 'test',
]); ]);
$this->delete(route('settings.source-controls.delete', $sourceControl->id)) Livewire::test(SourceControlsList::class)
->assertSessionDoesntHaveErrors(); ->callTableAction('delete', $sourceControl->id)
->assertSuccessful();
$this->assertDatabaseMissing('source_controls', [ $this->assertSoftDeleted('source_controls', [
'id' => $sourceControl->id, 'id' => $sourceControl->id,
]); ]);
} }
@ -89,12 +95,11 @@ public function test_cannot_delete_provider(string $provider): void
'source_control_id' => $sourceControl->id, 'source_control_id' => $sourceControl->id,
]); ]);
$this->delete(route('settings.source-controls.delete', $sourceControl->id)) Livewire::test(SourceControlsList::class)
->assertSessionDoesntHaveErrors() ->callTableAction('delete', $sourceControl->id)
->assertSessionHas('toast.type', 'error') ->assertNotified('This source control is being used by a site.');
->assertSessionHas('toast.message', 'This source control is being used by a site.');
$this->assertDatabaseHas('source_controls', [ $this->assertNotSoftDeleted('source_controls', [
'id' => $sourceControl->id, 'id' => $sourceControl->id,
]); ]);
} }
@ -115,10 +120,15 @@ public function test_edit_source_control(string $provider, ?string $url, array $
'url' => $url, 'url' => $url,
]); ]);
$this->post(route('settings.source-controls.update', $sourceControl->id), array_merge([ Livewire::test(SourceControlsList::class)
'name' => 'new-name', ->callTableAction('edit', $sourceControl->id, [
'url' => $url, 'name' => 'new-name',
], $input))->assertSessionDoesntHaveErrors(); 'token' => 'test', // for GitHub and Gitlab
'username' => 'test', // for Bitbucket
'password' => 'test', // for Bitbucket
'url' => $url, // for Gitlab
])
->assertSuccessful();
$sourceControl->refresh(); $sourceControl->refresh();

View File

@ -3,7 +3,10 @@
namespace Tests\Feature; namespace Tests\Feature;
use App\Models\SshKey; use App\Models\SshKey;
use App\Web\Pages\Settings\SSHKeys\Index;
use App\Web\Pages\Settings\SSHKeys\Widgets\SshKeysList;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Tests\TestCase; use Tests\TestCase;
class SshKeysTest extends TestCase class SshKeysTest extends TestCase
@ -14,10 +17,12 @@ public function test_create_ssh_key(): void
{ {
$this->actingAs($this->user); $this->actingAs($this->user);
$this->post(route('settings.ssh-keys.add'), [ Livewire::test(Index::class)
'name' => 'test', ->callAction('add', [
'public_key' => 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSUGPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XAt3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert/EnmZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbxNrRFi9wrf+M7Q== test@test.local', 'name' => 'test',
])->assertSessionDoesntHaveErrors(); 'public_key' => 'ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSUGPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XAt3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert/EnmZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbxNrRFi9wrf+M7Q== test@test.local',
])
->assertSuccessful();
} }
public function test_get_public_keys_list(): void public function test_get_public_keys_list(): void
@ -28,7 +33,7 @@ public function test_get_public_keys_list(): void
'user_id' => $this->user->id, 'user_id' => $this->user->id,
]); ]);
$this->get(route('settings.ssh-keys')) $this->get(Index::getUrl())
->assertSuccessful() ->assertSuccessful()
->assertSee($key->name); ->assertSee($key->name);
} }
@ -41,10 +46,11 @@ public function test_delete_key(): void
'user_id' => $this->user->id, 'user_id' => $this->user->id,
]); ]);
$this->delete(route('settings.ssh-keys.delete', $key->id)) Livewire::test(SshKeysList::class)
->assertSessionDoesntHaveErrors(); ->callTableAction('delete', $key->id)
->assertSuccessful();
$this->assertDatabaseMissing('ssh_keys', [ $this->assertSoftDeleted('ssh_keys', [
'id' => $key->id, 'id' => $key->id,
]); ]);
} }
@ -63,12 +69,13 @@ public function test_create_ssh_key_handles_invalid_or_partial_keys(array $postB
'public_key' => 'public-key-content', 'public_key' => 'public-key-content',
]); ]);
$response = $this->post(route('settings.ssh-keys.add'), $postBody); $response = Livewire::test(Index::class)
->callAction('add', $postBody);
if ($expectedToSucceed) { if ($expectedToSucceed) {
$response->assertSessionDoesntHaveErrors(); $response->assertHasNoActionErrors();
} else { } else {
$response->assertSessionHasErrors('public_key', 'Invalid key'); $response->assertHasActionErrors();
} }
} }

View File

@ -6,7 +6,10 @@
use App\Enums\SslType; use App\Enums\SslType;
use App\Facades\SSH; use App\Facades\SSH;
use App\Models\Ssl; use App\Models\Ssl;
use App\Web\Pages\Servers\Sites\Pages\SSL\Index;
use App\Web\Pages\Servers\Sites\Pages\SSL\Widgets\SslsList;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Tests\TestCase; use Tests\TestCase;
class SslTest extends TestCase class SslTest extends TestCase
@ -21,7 +24,7 @@ public function test_see_ssls_list()
'site_id' => $this->site->id, 'site_id' => $this->site->id,
]); ]);
$this->get(route('servers.sites.ssl', [ $this->get(Index::getUrl([
'server' => $this->server, 'server' => $this->server,
'site' => $this->site, 'site' => $this->site,
])) ]))
@ -29,30 +32,20 @@ public function test_see_ssls_list()
->assertSee($ssl->type); ->assertSee($ssl->type);
} }
public function test_see_ssls_list_with_no_ssls()
{
$this->actingAs($this->user);
$this->get(route('servers.sites.ssl', [
'server' => $this->server,
'site' => $this->site,
]))
->assertSuccessful()
->assertSeeText(__("You don't have any SSL certificates yet!"));
}
public function test_letsencrypt_ssl() public function test_letsencrypt_ssl()
{ {
SSH::fake('Successfully received certificate'); SSH::fake('Successfully received certificate');
$this->actingAs($this->user); $this->actingAs($this->user);
$this->post(route('servers.sites.ssl.store', [ Livewire::test(Index::class, [
'server' => $this->server, 'server' => $this->server,
'site' => $this->site, 'site' => $this->site,
]), [ ])
'type' => SslType::LETSENCRYPT, ->callAction('create', [
])->assertSessionDoesntHaveErrors(); 'type' => SslType::LETSENCRYPT,
])
->assertSuccessful();
$this->assertDatabaseHas('ssls', [ $this->assertDatabaseHas('ssls', [
'site_id' => $this->site->id, 'site_id' => $this->site->id,
@ -68,13 +61,15 @@ public function test_letsencrypt_ssl_with_aliases()
$this->actingAs($this->user); $this->actingAs($this->user);
$this->post(route('servers.sites.ssl.store', [ Livewire::test(Index::class, [
'server' => $this->server, 'server' => $this->server,
'site' => $this->site, 'site' => $this->site,
]), [ ])
'type' => SslType::LETSENCRYPT, ->callAction('create', [
'aliases' => '1', 'type' => SslType::LETSENCRYPT,
])->assertSessionDoesntHaveErrors(); 'aliases' => true,
])
->assertSuccessful();
$this->assertDatabaseHas('ssls', [ $this->assertDatabaseHas('ssls', [
'site_id' => $this->site->id, 'site_id' => $this->site->id,
@ -90,15 +85,17 @@ public function test_custom_ssl()
$this->actingAs($this->user); $this->actingAs($this->user);
$this->post(route('servers.sites.ssl.store', [ Livewire::test(Index::class, [
'server' => $this->server, 'server' => $this->server,
'site' => $this->site, 'site' => $this->site,
]), [ ])
'type' => SslType::CUSTOM, ->callAction('create', [
'certificate' => 'certificate', 'type' => SslType::CUSTOM,
'private' => 'private', 'certificate' => 'certificate',
'expires_at' => now()->addYear()->format('Y-m-d'), 'private' => 'private',
])->assertSessionDoesntHaveErrors(); 'expires_at' => now()->addYear()->format('Y-m-d'),
])
->assertSuccessful();
$this->assertDatabaseHas('ssls', [ $this->assertDatabaseHas('ssls', [
'site_id' => $this->site->id, 'site_id' => $this->site->id,
@ -117,11 +114,11 @@ public function test_delete_ssl()
'site_id' => $this->site->id, 'site_id' => $this->site->id,
]); ]);
$this->delete(route('servers.sites.ssl.destroy', [ Livewire::test(SslsList::class, [
'server' => $this->server,
'site' => $this->site, 'site' => $this->site,
'ssl' => $ssl, ])
]))->assertRedirect(); ->callTableAction('delete', $ssl->id)
->assertSuccessful();
$this->assertDatabaseMissing('ssls', [ $this->assertDatabaseMissing('ssls', [
'id' => $ssl->id, 'id' => $ssl->id,

View File

@ -7,12 +7,11 @@
use App\Models\Backup; use App\Models\Backup;
use App\Models\Database; use App\Models\Database;
use App\Models\StorageProvider as StorageProviderModel; use App\Models\StorageProvider as StorageProviderModel;
use App\StorageProviders\S3; use App\Web\Pages\Settings\StorageProviders\Index;
use App\StorageProviders\Wasabi; use App\Web\Pages\Settings\StorageProviders\Widgets\StorageProvidersList;
use Aws\S3\Exception\S3Exception;
use Aws\S3\S3Client;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Livewire\Livewire;
use Tests\TestCase; use Tests\TestCase;
class StorageProvidersTest extends TestCase class StorageProvidersTest extends TestCase
@ -34,8 +33,9 @@ public function test_create(array $input): void
FTP::fake(); FTP::fake();
} }
$this->post(route('settings.storage-providers.connect'), $input) Livewire::test(Index::class)
->assertSessionDoesntHaveErrors(); ->callAction('connect', $input)
->assertSuccessful();
if ($input['provider'] === StorageProvider::FTP) { if ($input['provider'] === StorageProvider::FTP) {
FTP::assertConnected($input['host']); FTP::assertConnected($input['host']);
@ -52,30 +52,30 @@ public function test_see_providers_list(): void
{ {
$this->actingAs($this->user); $this->actingAs($this->user);
$provider = \App\Models\StorageProvider::factory()->create([ $provider = StorageProviderModel::factory()->create([
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'provider' => StorageProvider::DROPBOX, 'provider' => StorageProvider::DROPBOX,
]); ]);
$this->get(route('settings.storage-providers')) $this->get(Index::getUrl())
->assertSuccessful() ->assertSuccessful()
->assertSee($provider->profile); ->assertSee($provider->profile);
$provider = \App\Models\StorageProvider::factory()->create([ $provider = StorageProviderModel::factory()->create([
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'provider' => StorageProvider::S3, 'provider' => StorageProvider::S3,
]); ]);
$this->get(route('settings.storage-providers')) $this->get(Index::getUrl())
->assertSuccessful() ->assertSuccessful()
->assertSee($provider->profile); ->assertSee($provider->profile);
$provider = \App\Models\StorageProvider::factory()->create([ $provider = StorageProviderModel::factory()->create([
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'provider' => StorageProvider::WASABI, 'provider' => StorageProvider::WASABI,
]); ]);
$this->get(route('settings.storage-providers')) $this->get(Index::getUrl())
->assertSuccessful() ->assertSuccessful()
->assertSee($provider->profile); ->assertSee($provider->profile);
} }
@ -84,12 +84,13 @@ public function test_delete_provider(): void
{ {
$this->actingAs($this->user); $this->actingAs($this->user);
$provider = \App\Models\StorageProvider::factory()->create([ $provider = StorageProviderModel::factory()->create([
'user_id' => $this->user->id, 'user_id' => $this->user->id,
]); ]);
$this->delete(route('settings.storage-providers.delete', $provider->id)) Livewire::test(StorageProvidersList::class)
->assertSessionDoesntHaveErrors(); ->callTableAction('delete', $provider->id)
->assertSuccessful();
$this->assertDatabaseMissing('storage_providers', [ $this->assertDatabaseMissing('storage_providers', [
'id' => $provider->id, 'id' => $provider->id,
@ -104,7 +105,7 @@ public function test_cannot_delete_provider(): void
'server_id' => $this->server, 'server_id' => $this->server,
]); ]);
$provider = \App\Models\StorageProvider::factory()->create([ $provider = StorageProviderModel::factory()->create([
'user_id' => $this->user->id, 'user_id' => $this->user->id,
]); ]);
@ -114,188 +115,15 @@ public function test_cannot_delete_provider(): void
'storage_id' => $provider->id, 'storage_id' => $provider->id,
]); ]);
$this->delete(route('settings.storage-providers.delete', $provider->id)) Livewire::test(StorageProvidersList::class)
->assertSessionDoesntHaveErrors() ->callTableAction('delete', $provider->id)
->assertSessionHas('toast.type', 'error') ->assertNotified('This storage provider is being used by a backup.');
->assertSessionHas('toast.message', 'This storage provider is being used by a backup.');
$this->assertDatabaseHas('storage_providers', [ $this->assertDatabaseHas('storage_providers', [
'id' => $provider->id, 'id' => $provider->id,
]); ]);
} }
public function test_s3_connect_successful()
{
$storageProvider = StorageProviderModel::factory()->create([
'provider' => StorageProvider::S3,
'credentials' => [
'key' => 'fake-key',
'secret' => 'fake-secret',
'region' => 'us-east-1',
'bucket' => 'fake-bucket',
'path' => '/',
],
]);
// Mock the S3Client
$s3ClientMock = $this->getMockBuilder(S3Client::class)
->disableOriginalConstructor()
->onlyMethods(['getCommand', 'execute'])
->getMock();
// Mock the getCommand method
$s3ClientMock->expects($this->once())
->method('getCommand')
->with('listBuckets')
->willReturn(new \Aws\Command('listBuckets'));
// Mock the execute method
$s3ClientMock->expects($this->once())
->method('execute')
->willReturn(['Buckets' => []]);
// Mock the S3 class to return the mocked S3Client
$s3 = $this->getMockBuilder(S3::class)
->setConstructorArgs([$storageProvider])
->onlyMethods(['getClient'])
->getMock();
$s3->expects($this->once())
->method('getClient')
->willReturn($s3ClientMock);
$this->assertTrue($s3->connect());
}
public function test_s3_connect_failure()
{
$storageProvider = StorageProviderModel::factory()->create([
'provider' => StorageProvider::S3,
'credentials' => [
'key' => 'fake-key',
'secret' => 'fake-secret',
'region' => 'us-east-1',
'bucket' => 'fake-bucket',
'path' => '/',
],
]);
// Mock the S3Client
$s3ClientMock = $this->getMockBuilder(S3Client::class)
->disableOriginalConstructor()
->onlyMethods(['getCommand', 'execute'])
->getMock();
// Mock the getCommand method
$s3ClientMock->expects($this->once())
->method('getCommand')
->with('listBuckets')
->willReturn(new \Aws\Command('listBuckets'));
// Mock the execute method to throw an S3Exception
$s3ClientMock->expects($this->once())
->method('execute')
->willThrowException(new S3Exception('Error', new \Aws\Command('ListBuckets')));
// Mock the S3 class to return the mocked S3Client
$s3 = $this->getMockBuilder(S3::class)
->setConstructorArgs([$storageProvider])
->onlyMethods(['getClient'])
->getMock();
$s3->expects($this->once())
->method('getClient')
->willReturn($s3ClientMock);
$this->assertFalse($s3->connect());
}
public function test_wasabi_connect_successful()
{
$storageProvider = StorageProviderModel::factory()->create([
'provider' => StorageProvider::WASABI,
'credentials' => [
'key' => 'fake-key',
'secret' => 'fake-secret',
'region' => 'us-east-1',
'bucket' => 'fake-bucket',
'path' => '/',
],
]);
// Mock the S3Client (Wasabi uses S3-compatible API)
$s3ClientMock = $this->getMockBuilder(S3Client::class)
->disableOriginalConstructor()
->onlyMethods(['getCommand', 'execute'])
->getMock();
// Mock the getCommand method
$s3ClientMock->expects($this->once())
->method('getCommand')
->with('listBuckets')
->willReturn(new \Aws\Command('listBuckets'));
// Mock the execute method
$s3ClientMock->expects($this->once())
->method('execute')
->willReturn(['Buckets' => []]);
// Mock the Wasabi class to return the mocked S3Client
$wasabi = $this->getMockBuilder(Wasabi::class)
->setConstructorArgs([$storageProvider])
->onlyMethods(['getClient'])
->getMock();
$wasabi->expects($this->once())
->method('getClient')
->willReturn($s3ClientMock);
$this->assertTrue($wasabi->connect());
}
public function test_wasabi_connect_failure()
{
$storageProvider = StorageProviderModel::factory()->create([
'provider' => StorageProvider::WASABI,
'credentials' => [
'key' => 'fake-key',
'secret' => 'fake-secret',
'region' => 'us-east-1',
'bucket' => 'fake-bucket',
'path' => '/',
],
]);
// Mock the S3Client (Wasabi uses S3-compatible API)
$s3ClientMock = $this->getMockBuilder(S3Client::class)
->disableOriginalConstructor()
->onlyMethods(['getCommand', 'execute'])
->getMock();
// Mock the getCommand method
$s3ClientMock->expects($this->once())
->method('getCommand')
->with('listBuckets')
->willReturn(new \Aws\Command('listBuckets'));
// Mock the execute method to throw an S3Exception
$s3ClientMock->expects($this->once())
->method('execute')
->willThrowException(new S3Exception('Error', new \Aws\Command('ListBuckets')));
// Mock the Wasabi class to return the mocked S3Client
$wasabi = $this->getMockBuilder(Wasabi::class)
->setConstructorArgs([$storageProvider])
->onlyMethods(['getClient'])
->getMock();
$wasabi->expects($this->once())
->method('getClient')
->willReturn($s3ClientMock);
$this->assertFalse($wasabi->connect());
}
/** /**
* @TODO: complete FTP tests * @TODO: complete FTP tests
*/ */

View File

@ -2,10 +2,13 @@
namespace Tests\Feature; namespace Tests\Feature;
use App\Models\Server;
use App\Models\Site;
use App\Models\Tag; use App\Models\Tag;
use App\Web\Pages\Servers\Sites\Widgets\SiteDetails;
use App\Web\Pages\Servers\Widgets\ServerDetails;
use App\Web\Pages\Settings\Tags\Index;
use App\Web\Pages\Settings\Tags\Widgets\TagsList;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Tests\TestCase; use Tests\TestCase;
class TagsTest extends TestCase class TagsTest extends TestCase
@ -16,10 +19,12 @@ public function test_create_tag(): void
{ {
$this->actingAs($this->user); $this->actingAs($this->user);
$this->post(route('settings.tags.create'), [ Livewire::test(Index::class)
'name' => 'test', ->callAction('create', [
'color' => config('core.tag_colors')[0], 'name' => 'test',
])->assertSessionDoesntHaveErrors(); 'color' => config('core.tag_colors')[0],
])
->assertSuccessful();
$this->assertDatabaseHas('tags', [ $this->assertDatabaseHas('tags', [
'project_id' => $this->user->current_project_id, 'project_id' => $this->user->current_project_id,
@ -36,7 +41,7 @@ public function test_get_tags_list(): void
'project_id' => $this->user->current_project_id, 'project_id' => $this->user->current_project_id,
]); ]);
$this->get(route('settings.tags')) $this->get(Index::getUrl())
->assertSuccessful() ->assertSuccessful()
->assertSee($tag->name); ->assertSee($tag->name);
} }
@ -49,8 +54,9 @@ public function test_delete_tag(): void
'project_id' => $this->user->current_project_id, 'project_id' => $this->user->current_project_id,
]); ]);
$this->delete(route('settings.tags.delete', $tag->id)) Livewire::test(TagsList::class)
->assertSessionDoesntHaveErrors(); ->callTableAction('delete', $tag->id)
->assertSuccessful();
$this->assertDatabaseMissing('tags', [ $this->assertDatabaseMissing('tags', [
'id' => $tag->id, 'id' => $tag->id,
@ -61,20 +67,24 @@ public function test_create_tag_handles_invalid_color(): void
{ {
$this->actingAs($this->user); $this->actingAs($this->user);
$this->post(route('settings.tags.create'), [ Livewire::test(Index::class)
'name' => 'test', ->callAction('create', [
'color' => 'invalid-color', 'name' => 'test',
])->assertSessionHasErrors('color'); 'color' => 'invalid-color',
])
->assertHasActionErrors();
} }
public function test_create_tag_handles_invalid_name(): void public function test_create_tag_handles_invalid_name(): void
{ {
$this->actingAs($this->user); $this->actingAs($this->user);
$this->post(route('settings.tags.create'), [ Livewire::test(Index::class)
'name' => '', ->callAction('create', [
'color' => config('core.tag_colors')[0], 'name' => '',
])->assertSessionHasErrors('name'); 'color' => config('core.tag_colors')[0],
])
->assertHasActionErrors();
} }
public function test_edit_tag(): void public function test_edit_tag(): void
@ -85,11 +95,12 @@ public function test_edit_tag(): void
'project_id' => $this->user->current_project_id, 'project_id' => $this->user->current_project_id,
]); ]);
$this->post(route('settings.tags.update', ['tag' => $tag]), [ Livewire::test(TagsList::class)
'name' => 'New Name', ->callTableAction('edit', $tag->id, [
'color' => config('core.tag_colors')[1], 'name' => 'New Name',
]) 'color' => config('core.tag_colors')[1],
->assertSessionDoesntHaveErrors(); ])
->assertSuccessful();
$this->assertDatabaseHas('tags', [ $this->assertDatabaseHas('tags', [
'id' => $tag->id, 'id' => $tag->id,
@ -98,104 +109,101 @@ public function test_edit_tag(): void
]); ]);
} }
/** public function test_attach_existing_tag_to_server(): void
* @dataProvider data
*/
public function test_attach_existing_tag_to_taggable(array $input): void
{ {
$this->actingAs($this->user); $this->actingAs($this->user);
$tag = Tag::factory()->create([ $tag = Tag::factory()->create([
'project_id' => $this->user->current_project_id, 'project_id' => $this->user->current_project_id,
'name' => $input['name'], 'name' => 'staging',
]); ]);
$input['taggable_id'] = match ($input['taggable_type']) { Livewire::test(ServerDetails::class, [
Server::class => $this->server->id, 'server' => $this->server,
Site::class => $this->site->id, ])
default => $this->fail('Unknown taggable type'), ->callInfolistAction('tags.*', 'edit_tags', [
}; 'tags' => [$tag->id],
])
$this->post(route('tags.attach'), $input)->assertSessionDoesntHaveErrors(); ->assertSuccessful();
$this->assertDatabaseHas('taggables', [ $this->assertDatabaseHas('taggables', [
'taggable_id' => $input['taggable_id'], 'taggable_id' => $this->server->id,
'tag_id' => $tag->id, 'tag_id' => $tag->id,
]); ]);
} }
/** public function test_detach_tag_from_server(): void
* @dataProvider data
*/
public function test_attach_new_tag_to_taggable(array $input): void
{
$this->actingAs($this->user);
$input['taggable_id'] = match ($input['taggable_type']) {
Server::class => $this->server->id,
Site::class => $this->site->id,
default => $this->fail('Unknown taggable type'),
};
$this->post(route('tags.attach'), $input)->assertSessionDoesntHaveErrors();
$this->assertDatabaseHas('tags', [
'name' => $input['name'],
]);
$tag = Tag::query()->where('name', $input['name'])->firstOrFail();
$this->assertDatabaseHas('taggables', [
'taggable_id' => $input['taggable_id'],
'tag_id' => $tag->id,
]);
}
/**
* @dataProvider data
*/
public function test_detach_tag(array $input): void
{ {
$this->actingAs($this->user); $this->actingAs($this->user);
$tag = Tag::factory()->create([ $tag = Tag::factory()->create([
'project_id' => $this->user->current_project_id, 'project_id' => $this->user->current_project_id,
'name' => $input['name'], 'name' => 'staging',
]); ]);
$taggable = match ($input['taggable_type']) { $this->server->tags()->attach($tag);
Server::class => $this->server,
Site::class => $this->site,
default => $this->fail('Unknown taggable type'),
};
$input['taggable_id'] = $taggable->id; Livewire::test(ServerDetails::class, [
'server' => $this->server,
$taggable->tags()->attach($tag); ])
->callInfolistAction('tags.*', 'edit_tags', [
$this->post(route('tags.detach', $tag->id), $input)->assertSessionDoesntHaveErrors(); 'tags' => [],
])
->assertSuccessful();
$this->assertDatabaseMissing('taggables', [ $this->assertDatabaseMissing('taggables', [
'taggable_id' => $input['taggable_id'], 'taggable_id' => $this->server->id,
'tag_id' => $tag->id, 'tag_id' => $tag->id,
]); ]);
} }
public static function data(): array public function test_attach_existing_tag_to_site(): void
{ {
return [ $this->actingAs($this->user);
[
[ $tag = Tag::factory()->create([
'taggable_type' => Server::class, 'project_id' => $this->user->current_project_id,
'name' => 'staging', 'name' => 'staging',
], ]);
],
[ Livewire::test(SiteDetails::class, [
[ 'server' => $this->server,
'taggable_type' => Site::class, 'site' => $this->site,
'name' => 'production', ])
], ->callInfolistAction('tags.*', 'edit_tags', [
], 'tags' => [$tag->id],
]; ])
->assertSuccessful();
$this->assertDatabaseHas('taggables', [
'taggable_id' => $this->site->id,
'tag_id' => $tag->id,
]);
}
public function test_detach_tag_from_site(): void
{
$this->actingAs($this->user);
$tag = Tag::factory()->create([
'project_id' => $this->user->current_project_id,
'name' => 'staging',
]);
$this->site->tags()->attach($tag);
Livewire::test(SiteDetails::class, [
'server' => $this->server,
'site' => $this->site,
])
->callInfolistAction('tags.*', 'edit_tags', [
'tags' => [],
])
->assertSuccessful();
$this->assertDatabaseMissing('taggables', [
'taggable_id' => $this->site->id,
'tag_id' => $tag->id,
]);
} }
} }

View File

@ -0,0 +1,103 @@
<?php
namespace Tests\Unit\StorageProviders;
use App\Enums\StorageProvider;
use App\Models\StorageProvider as StorageProviderModel;
use App\StorageProviders\S3;
use Aws\Command;
use Aws\S3\Exception\S3Exception;
use Aws\S3\S3Client;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class S3Test extends TestCase
{
use RefreshDatabase;
public function test_s3_connect_successful()
{
$storageProvider = StorageProviderModel::factory()->create([
'provider' => StorageProvider::S3,
'credentials' => [
'key' => 'fake-key',
'secret' => 'fake-secret',
'region' => 'us-east-1',
'bucket' => 'fake-bucket',
'path' => '/',
],
]);
// Mock the S3Client
$s3ClientMock = $this->getMockBuilder(S3Client::class)
->disableOriginalConstructor()
->onlyMethods(['getCommand', 'execute'])
->getMock();
// Mock the getCommand method
$s3ClientMock->expects($this->once())
->method('getCommand')
->with('listBuckets')
->willReturn(new Command('listBuckets'));
// Mock the execute method
$s3ClientMock->expects($this->once())
->method('execute')
->willReturn(['Buckets' => []]);
// Mock the S3 class to return the mocked S3Client
$s3 = $this->getMockBuilder(S3::class)
->setConstructorArgs([$storageProvider])
->onlyMethods(['getClient'])
->getMock();
$s3->expects($this->once())
->method('getClient')
->willReturn($s3ClientMock);
$this->assertTrue($s3->connect());
}
public function test_s3_connect_failure()
{
$storageProvider = StorageProviderModel::factory()->create([
'provider' => StorageProvider::S3,
'credentials' => [
'key' => 'fake-key',
'secret' => 'fake-secret',
'region' => 'us-east-1',
'bucket' => 'fake-bucket',
'path' => '/',
],
]);
// Mock the S3Client
$s3ClientMock = $this->getMockBuilder(S3Client::class)
->disableOriginalConstructor()
->onlyMethods(['getCommand', 'execute'])
->getMock();
// Mock the getCommand method
$s3ClientMock->expects($this->once())
->method('getCommand')
->with('listBuckets')
->willReturn(new Command('listBuckets'));
// Mock the execute method to throw an S3Exception
$s3ClientMock->expects($this->once())
->method('execute')
->willThrowException(new S3Exception('Error', new Command('ListBuckets')));
// Mock the S3 class to return the mocked S3Client
$s3 = $this->getMockBuilder(S3::class)
->setConstructorArgs([$storageProvider])
->onlyMethods(['getClient'])
->getMock();
$s3->expects($this->once())
->method('getClient')
->willReturn($s3ClientMock);
$this->assertFalse($s3->connect());
}
}

View File

@ -0,0 +1,104 @@
<?php
namespace Tests\Unit\StorageProviders;
use App\Enums\StorageProvider;
use App\Models\StorageProvider as StorageProviderModel;
use App\StorageProviders\S3;
use App\StorageProviders\Wasabi;
use Aws\Command;
use Aws\S3\Exception\S3Exception;
use Aws\S3\S3Client;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class WasabiTest extends TestCase
{
use RefreshDatabase;
public function test_wasabi_connect_successful()
{
$storageProvider = StorageProviderModel::factory()->create([
'provider' => StorageProvider::WASABI,
'credentials' => [
'key' => 'fake-key',
'secret' => 'fake-secret',
'region' => 'us-east-1',
'bucket' => 'fake-bucket',
'path' => '/',
],
]);
// Mock the S3Client (Wasabi uses S3-compatible API)
$s3ClientMock = $this->getMockBuilder(S3Client::class)
->disableOriginalConstructor()
->onlyMethods(['getCommand', 'execute'])
->getMock();
// Mock the getCommand method
$s3ClientMock->expects($this->once())
->method('getCommand')
->with('listBuckets')
->willReturn(new Command('listBuckets'));
// Mock the execute method
$s3ClientMock->expects($this->once())
->method('execute')
->willReturn(['Buckets' => []]);
// Mock the Wasabi class to return the mocked S3Client
$wasabi = $this->getMockBuilder(Wasabi::class)
->setConstructorArgs([$storageProvider])
->onlyMethods(['getClient'])
->getMock();
$wasabi->expects($this->once())
->method('getClient')
->willReturn($s3ClientMock);
$this->assertTrue($wasabi->connect());
}
public function test_wasabi_connect_failure()
{
$storageProvider = StorageProviderModel::factory()->create([
'provider' => StorageProvider::WASABI,
'credentials' => [
'key' => 'fake-key',
'secret' => 'fake-secret',
'region' => 'us-east-1',
'bucket' => 'fake-bucket',
'path' => '/',
],
]);
// Mock the S3Client (Wasabi uses S3-compatible API)
$s3ClientMock = $this->getMockBuilder(S3Client::class)
->disableOriginalConstructor()
->onlyMethods(['getCommand', 'execute'])
->getMock();
// Mock the getCommand method
$s3ClientMock->expects($this->once())
->method('getCommand')
->with('listBuckets')
->willReturn(new Command('listBuckets'));
// Mock the execute method to throw an S3Exception
$s3ClientMock->expects($this->once())
->method('execute')
->willThrowException(new S3Exception('Error', new Command('ListBuckets')));
// Mock the Wasabi class to return the mocked S3Client
$wasabi = $this->getMockBuilder(Wasabi::class)
->setConstructorArgs([$storageProvider])
->onlyMethods(['getClient'])
->getMock();
$wasabi->expects($this->once())
->method('getClient')
->willReturn($s3ClientMock);
$this->assertFalse($wasabi->connect());
}
}