increase test coverage (#117)

70% test coverage
remove socialite
This commit is contained in:
Saeed Vaziry
2024-03-15 22:23:45 +01:00
committed by GitHub
parent 4f12de9586
commit a406491160
62 changed files with 1102 additions and 639 deletions

View File

@ -27,8 +27,7 @@ public function link(DatabaseUser $databaseUser, array $input): void
->whereIn('name', $input['databases'])
->count();
if (count($input['databases']) !== $dbs) {
throw ValidationException::withMessages(['databases' => __('Databases not found!')])
->errorBag('linkUser');
throw ValidationException::withMessages(['databases' => __('Databases not found!')]);
}
$databaseUser->databases = $input['databases'];

View File

@ -9,7 +9,7 @@
class RunBackup
{
public function run(Backup $backup): void
public function run(Backup $backup): BackupFile
{
$file = new BackupFile([
'backup_id' => $backup->id,
@ -26,5 +26,7 @@ public function run(Backup $backup): void
$file->status = BackupFileStatus::FAILED;
$file->save();
})->onConnection('ssh');
return $file;
}
}

View File

@ -3,7 +3,6 @@
namespace App\Actions\Queue;
use App\Enums\QueueStatus;
use App\Enums\ServiceStatus;
use App\Models\Queue;
class ManageQueue
@ -14,10 +13,7 @@ public function start(Queue $queue): void
$queue->save();
dispatch(function () use ($queue) {
$queue->server->processManager()->handler()->start($queue->id, $queue->site_id);
$queue->status = ServiceStatus::READY;
$queue->save();
})->catch(function () use ($queue) {
$queue->status = ServiceStatus::FAILED;
$queue->status = QueueStatus::RUNNING;
$queue->save();
})->onConnection('ssh');
}
@ -28,10 +24,7 @@ public function stop(Queue $queue): void
$queue->save();
dispatch(function () use ($queue) {
$queue->server->processManager()->handler()->stop($queue->id, $queue->site_id);
$queue->status = ServiceStatus::STOPPED;
$queue->save();
})->catch(function () use ($queue) {
$queue->status = ServiceStatus::FAILED;
$queue->status = QueueStatus::STOPPED;
$queue->save();
})->onConnection('ssh');
}
@ -42,10 +35,7 @@ public function restart(Queue $queue): void
$queue->save();
dispatch(function () use ($queue) {
$queue->server->processManager()->handler()->restart($queue->id, $queue->site_id);
$queue->status = ServiceStatus::READY;
$queue->save();
})->catch(function () use ($queue) {
$queue->status = ServiceStatus::FAILED;
$queue->status = QueueStatus::RUNNING;
$queue->save();
})->onConnection('ssh');
}

View File

@ -1,41 +0,0 @@
<?php
namespace App\Actions\Script;
use App\Models\Script;
use App\Models\User;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class CreateScript
{
/**
* @throws ValidationException
*/
public function handle(User $creator, array $input): Script
{
$this->validateInputs($input);
$script = new Script([
'user_id' => $creator->id,
'name' => $input['name'],
'content' => $input['content'],
]);
$script->save();
return $script;
}
/**
* @throws ValidationException
*/
private function validateInputs(array $input): void
{
$rules = [
'name' => 'required',
'content' => 'required',
];
Validator::make($input, $rules)->validateWithBag('createScript');
}
}

View File

@ -1,17 +0,0 @@
<?php
namespace App\Actions\Script;
use App\Models\User;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
class GetScripts
{
public function handle(User $user): LengthAwarePaginator
{
return $user->scripts()
->orderBy('id', 'desc')
->paginate(6)
->onEachSide(1);
}
}

View File

@ -1,37 +0,0 @@
<?php
namespace App\Actions\Script;
use App\Models\Script;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class UpdateScript
{
/**
* @throws ValidationException
*/
public function handle(Script $script, array $input): Script
{
$this->validateInputs($input);
$script->name = $input['name'];
$script->content = $input['content'];
$script->save();
return $script;
}
/**
* @throws ValidationException
*/
private function validateInputs(array $input): void
{
$rules = [
'name' => 'required',
'content' => 'required',
];
Validator::make($input, $rules)->validateWithBag('updateScript');
}
}

View File

@ -1,14 +0,0 @@
<?php
namespace App\Actions\Server;
use App\Models\Server;
use Illuminate\Database\Eloquent\Collection;
class GetServers
{
public function handle(): Collection
{
return Server::query()->latest()->get();
}
}

View File

@ -28,7 +28,7 @@ public function create(User $user, array $input): ServerProvider
} catch (Exception) {
throw ValidationException::withMessages([
'provider' => [
__("Couldn't connect to provider. Please check your credentials and try again later."),
sprintf("Couldn't connect to %s. Please check your credentials.", $input['provider']),
],
]);
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Actions\ServerProvider;
use App\Models\ServerProvider;
use Exception;
class DeleteServerProvider
{
/**
* @throws Exception
*/
public function delete(ServerProvider $serverProvider): void
{
if ($serverProvider->servers()->exists()) {
throw new Exception('This server provider is being used by a server.');
}
$serverProvider->delete();
}
}

View File

@ -19,9 +19,6 @@ public function start(Service $service): void
$service->status = ServiceStatus::FAILED;
}
$service->save();
})->catch(function () use ($service) {
$service->status = ServiceStatus::FAILED;
$service->save();
})->onConnection('ssh');
}
@ -37,9 +34,6 @@ public function stop(Service $service): void
$service->status = ServiceStatus::FAILED;
}
$service->save();
})->catch(function () use ($service) {
$service->status = ServiceStatus::FAILED;
$service->save();
})->onConnection('ssh');
}
@ -55,9 +49,6 @@ public function restart(Service $service): void
$service->status = ServiceStatus::FAILED;
}
$service->save();
})->catch(function () use ($service) {
$service->status = ServiceStatus::FAILED;
$service->save();
})->onConnection('ssh');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Actions\SourceControl;
use App\Models\SourceControl;
class DeleteSourceControl
{
public function delete(SourceControl $sourceControl): void
{
if ($sourceControl->sites()->exists()) {
throw new \Exception('This source control is being used by a site.');
}
$sourceControl->delete();
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Actions\StorageProvider;
use App\Models\StorageProvider;
use Exception;
class DeleteStorageProvider
{
/**
* @throws Exception
*/
public function delete(StorageProvider $storageProvider): void
{
if ($storageProvider->backups()->exists()) {
throw new Exception('This storage provider is being used by a backup.');
}
$storageProvider->delete();
}
}

View File

@ -15,14 +15,19 @@ class RunBackupCommand extends Command
public function handle(): void
{
$total = 0;
Backup::query()
->where('interval', $this->argument('interval'))
->where('status', BackupStatus::RUNNING)
->chunk(100, function ($backups) {
->chunk(100, function ($backups) use (&$total) {
/** @var Backup $backup */
foreach ($backups as $backup) {
app(RunBackup::class)->run($backup);
$total++;
}
});
$this->info("{$total} backups started");
}
}

View File

@ -2,13 +2,8 @@
namespace App\Exceptions;
use App\Models\SourceControl;
use Exception;
class SourceControlIsNotConnected extends Exception
{
public function __construct(protected SourceControl|string|null $sourceControl, ?string $message = null)
{
parent::__construct($message ?? 'Source control is not connected');
}
}

View File

@ -88,7 +88,7 @@ public function connect(bool $sftp = false): void
Log::error('Error connecting', [
'msg' => $e->getMessage(),
]);
throw $e;
throw new SSHConnectionError($e->getMessage());
}
}

View File

@ -47,11 +47,14 @@ public function update(Request $request, Project $project): HtmxResponse
public function delete(Project $project): RedirectResponse
{
/** @var User $user */
$user = auth()->user();
/** @var Project $project */
$project = auth()->user()->projects()->findOrFail($project->id);
$project = $user->projects()->findOrFail($project->id);
try {
app(DeleteProject::class)->delete(auth()->user(), $project);
app(DeleteProject::class)->delete($user, $project);
} catch (ValidationException $e) {
Toast::error($e->getMessage());

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Settings;
use App\Actions\ServerProvider\CreateServerProvider;
use App\Actions\ServerProvider\DeleteServerProvider;
use App\Facades\Toast;
use App\Helpers\HtmxResponse;
use App\Http\Controllers\Controller;
@ -32,14 +33,15 @@ public function connect(Request $request): HtmxResponse
return htmx()->redirect(route('server-providers'));
}
/**
* @TODO Update servers using this provider
*/
public function delete(int $id): RedirectResponse
public function delete(ServerProvider $serverProvider): RedirectResponse
{
$serverProvider = ServerProvider::query()->findOrFail($id);
try {
app(DeleteServerProvider::class)->delete($serverProvider);
} catch (\Exception $e) {
Toast::error($e->getMessage());
$serverProvider->delete();
return back();
}
Toast::success('Server provider deleted.');

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Settings;
use App\Actions\SourceControl\ConnectSourceControl;
use App\Actions\SourceControl\DeleteSourceControl;
use App\Facades\Toast;
use App\Helpers\HtmxResponse;
use App\Http\Controllers\Controller;
@ -11,9 +12,6 @@
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
/**
* @TODO Assign user to source control
*/
class SourceControlController extends Controller
{
public function index(): View
@ -34,11 +32,15 @@ public function connect(Request $request): HtmxResponse
return htmx()->redirect(route('source-controls'));
}
public function delete(int $id): RedirectResponse
public function delete(SourceControl $sourceControl): RedirectResponse
{
$sourceControl = SourceControl::query()->findOrFail($id);
try {
app(DeleteSourceControl::class)->delete($sourceControl);
} catch (\Exception $e) {
Toast::error($e->getMessage());
$sourceControl->delete();
return back();
}
Toast::success('Source control deleted.');

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Settings;
use App\Actions\StorageProvider\CreateStorageProvider;
use App\Actions\StorageProvider\DeleteStorageProvider;
use App\Facades\Toast;
use App\Helpers\HtmxResponse;
use App\Http\Controllers\Controller;
@ -32,14 +33,15 @@ public function connect(Request $request): HtmxResponse
return htmx()->redirect(route('storage-providers'));
}
/**
* @TODO Update servers using this provider
*/
public function delete(int $id): RedirectResponse
public function delete(StorageProvider $storageProvider): RedirectResponse
{
$storageProvider = StorageProvider::query()->findOrFail($id);
try {
app(DeleteStorageProvider::class)->delete($storageProvider);
} catch (\Exception $e) {
Toast::error($e->getMessage());
$storageProvider->delete();
return back();
}
Toast::success('Storage provider deleted.');

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\Facades\Toast;
use App\Helpers\HtmxResponse;
use App\Models\Server;
use App\Models\Site;
use Illuminate\Contracts\View\View;
@ -43,7 +44,7 @@ public function updateVhost(Server $server, Site $site, Request $request): Redir
return back();
}
public function updatePHPVersion(Server $server, Site $site, Request $request): RedirectResponse
public function updatePHPVersion(Server $server, Site $site, Request $request): HtmxResponse
{
$this->validate($request, [
'version' => [
@ -60,6 +61,6 @@ public function updatePHPVersion(Server $server, Site $site, Request $request):
Toast::error($e->getMessage());
}
return back();
return htmx()->back();
}
}

View File

@ -1,36 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $site_id
* @property int $mode
* @property string $from
* @property string $to
* @property string $status
*/
class Redirect extends AbstractModel
{
use HasFactory;
protected $fillable = [
'site_id',
'mode',
'from',
'to',
'status',
];
protected $casts = [
'site_id' => 'integer',
'mode' => 'integer',
];
public function site(): BelongsTo
{
return $this->belongsTo(Site::class);
}
}

View File

@ -1,38 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* @property int $user_id
* @property string $name
* @property string $content
* @property User $creator
*/
class Script extends AbstractModel
{
use HasFactory;
protected $fillable = [
'user_id',
'name',
'content',
];
protected $casts = [
'user_id' => 'integer',
];
public function creator(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function executions(): HasMany
{
return $this->hasMany(ScriptExecution::class, 'script_id');
}
}

View File

@ -1,42 +0,0 @@
<?php
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $script_id
* @property int $server_id
* @property string $user
* @property Carbon $finished_at
* @property ?Server $server
* @property Script $script
*/
class ScriptExecution extends AbstractModel
{
use HasFactory;
protected $fillable = [
'script_id',
'server_id',
'user',
'finished_at',
];
protected $casts = [
'script_id' => 'integer',
'server_id' => 'integer',
];
public function script(): BelongsTo
{
return $this->belongsTo(Script::class);
}
public function server(): BelongsTo
{
return $this->belongsTo(Server::class);
}
}

View File

@ -51,7 +51,6 @@
* @property FirewallRule[] $firewallRules
* @property CronJob[] $cronJobs
* @property Queue[] $queues
* @property ScriptExecution[] $scriptExecutions
* @property Backup[] $backups
* @property Queue[] $daemons
* @property SshKey[] $sshKeys
@ -121,7 +120,6 @@ public static function boot(): void
$server->cronJobs()->delete();
$server->queues()->delete();
$server->daemons()->delete();
$server->scriptExecutions()->delete();
$server->sshKeys()->detach();
if (File::exists($server->sshKey()['public_key_path'])) {
File::delete($server->sshKey()['public_key_path']);
@ -187,11 +185,6 @@ public function queues(): HasMany
return $this->hasMany(Queue::class);
}
public function scriptExecutions(): HasMany
{
return $this->hasMany(ScriptExecution::class);
}
public function backups(): HasMany
{
return $this->hasMany(Backup::class);

View File

@ -4,6 +4,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
@ -38,8 +39,12 @@ public static function boot(): void
parent::boot();
static::deleting(function (ServerLog $log) {
if (Storage::disk($log->disk)->exists($log->name)) {
Storage::disk($log->disk)->delete($log->name);
try {
if (Storage::disk($log->disk)->exists($log->name)) {
Storage::disk($log->disk)->delete($log->name);
}
} catch (\Exception $e) {
Log::error($e->getMessage(), ['exception' => $e]);
}
});
}

View File

@ -4,6 +4,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* @property int $user_id
@ -40,4 +41,9 @@ public function getCredentials(): array
{
return $this->credentials;
}
public function servers(): HasMany
{
return $this->hasMany(Server::class, 'provider_id');
}
}

View File

@ -33,7 +33,6 @@
* @property Deployment[] $deployments
* @property ?GitHook $gitHook
* @property DeploymentScript $deploymentScript
* @property Redirect[] $redirects
* @property Queue[] $queues
* @property Ssl[] $ssls
* @property ?Ssl $activeSsl
@ -76,7 +75,6 @@ public static function boot(): void
parent::boot();
static::deleting(function (Site $site) {
$site->redirects()->delete();
$site->queues()->delete();
$site->ssls()->delete();
$site->deployments()->delete();
@ -116,11 +114,6 @@ public function deploymentScript(): HasOne
return $this->hasOne(DeploymentScript::class);
}
public function redirects(): HasMany
{
return $this->hasMany(Redirect::class);
}
public function queues(): HasMany
{
return $this->hasMany(Queue::class);
@ -192,8 +185,8 @@ public function php(): ?Service
public function changePHPVersion($version): void
{
$this->php_version = $version;
$this->server->webserver()->handler()->changePHPVersion($this, $version);
$this->php_version = $version;
$this->save();
}

View File

@ -4,6 +4,7 @@
use App\SourceControlProviders\SourceControlProvider;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* @property string $provider
@ -37,4 +38,9 @@ public function getRepo(?string $repo = null): ?array
{
return $this->provider()->getRepo($repo);
}
public function sites(): HasMany
{
return $this->hasMany(Site::class);
}
}

View File

@ -4,6 +4,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* @property int $user_id
@ -39,4 +40,9 @@ public function provider(): \App\StorageProviders\StorageProvider
return new $providerClass($this);
}
public function backups(): HasMany
{
return $this->hasMany(Backup::class, 'storage_id');
}
}

View File

@ -86,11 +86,6 @@ public function serverProviders(): HasMany
return $this->hasMany(ServerProvider::class);
}
public function scripts(): HasMany
{
return $this->hasMany(Script::class, 'user_id');
}
public function sourceControl(string $provider): HasOne
{
return $this->hasOne(SourceControl::class)->where('provider', $provider);

View File

@ -60,6 +60,6 @@ private function sendToTelegram(string $text): void
'text' => $text,
'parse_mode' => 'markdown',
'disable_web_page_preview' => true,
]);
])->throw();
}
}

View File

@ -5,7 +5,6 @@
use App\Helpers\Notifier;
use App\Helpers\SSH;
use App\Helpers\Toast;
use App\Support\SocialiteProviders\DropboxProvider;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Support\Facades\URL;
@ -39,26 +38,8 @@ public function boot(): void
return new Toast;
});
$this->extendSocialite();
if (str(config('app.url'))->startsWith('https://')) {
URL::forceScheme('https');
}
}
/**
* @throws BindingResolutionException
*/
private function extendSocialite(): void
{
$socialite = $this->app->make('Laravel\Socialite\Contracts\Factory');
$socialite->extend(
'dropbox',
function ($app) use ($socialite) {
$config = $app['config']['services.dropbox'];
return $socialite->buildProvider(DropboxProvider::class, $config);
}
);
}
}

View File

@ -1,78 +0,0 @@
<?php
namespace App\Support\SocialiteProviders;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Arr;
use Laravel\Socialite\Two\AbstractProvider;
use Laravel\Socialite\Two\ProviderInterface;
use Laravel\Socialite\Two\User;
class DropboxProvider extends AbstractProvider implements ProviderInterface
{
/**
* Unique Provider Identifier.
*/
public const IDENTIFIER = 'DROPBOX';
/**
* {@inheritdoc}
*/
protected $scopeSeparator = ' ';
/**
* {@inheritdoc}
*/
protected function getAuthUrl($state): string
{
return $this->buildAuthUrlFromBase('https://www.dropbox.com/oauth2/authorize', $state);
}
/**
* {@inheritdoc}
*/
protected function getTokenUrl(): string
{
return 'https://api.dropboxapi.com/oauth2/token';
}
/**
* {@inheritdoc}
*/
protected function getTokenFields($code): array
{
return array_merge(parent::getTokenFields($code), [
'grant_type' => 'authorization_code',
]);
}
/**
* {@inheritdoc}
*
* @throws GuzzleException
*/
protected function getUserByToken($token)
{
$response = $this->getHttpClient()->post('https://api.dropboxapi.com/2/users/get_current_account', [
'headers' => [
'Authorization' => 'Bearer '.$token,
],
]);
return json_decode($response->getBody(), true);
}
/**
* {@inheritdoc}
*/
protected function mapUserToObject(array $user): User
{
return (new User)->setRaw($user)->map([
'id' => $user['account_id'],
'nickname' => null,
'name' => $user['name']['display_name'],
'email' => $user['email'],
'avatar' => Arr::get($user, 'profile_photo_url'),
]);
}
}

View File

@ -2,6 +2,7 @@
namespace App\Support\Testing;
use App\Exceptions\SSHConnectionError;
use App\Helpers\SSH;
use Illuminate\Support\Traits\ReflectsClosures;
use PHPUnit\Framework\Assert;
@ -14,11 +15,25 @@ class SSHFake extends SSH
protected ?string $output;
protected bool $connectionWillFail = false;
public function __construct(?string $output = null)
{
$this->output = $output;
}
public function connectionWillFail(): void
{
$this->connectionWillFail = true;
}
public function connect(bool $sftp = false): void
{
if ($this->connectionWillFail) {
throw new SSHConnectionError('Connection failed');
}
}
public function exec(string|array $commands, string $log = '', ?int $siteId = null): string
{
if ($log) {
@ -45,6 +60,11 @@ public function exec(string|array $commands, string $log = '', ?int $siteId = nu
return $output;
}
public function upload(string $local, string $remote): void
{
$this->log = null;
}
public function assertExecuted(array|string $commands): void
{
if (! $this->commands) {