Run proxy restart as background job with real-time logs (#7475)
This commit is contained in:
commit
9e0fa03434
6 changed files with 378 additions and 18 deletions
|
|
@ -14,12 +14,15 @@ class ProxyStatusChangedUI implements ShouldBroadcast
|
||||||
|
|
||||||
public ?int $teamId = null;
|
public ?int $teamId = null;
|
||||||
|
|
||||||
public function __construct(?int $teamId = null)
|
public ?int $activityId = null;
|
||||||
|
|
||||||
|
public function __construct(?int $teamId = null, ?int $activityId = null)
|
||||||
{
|
{
|
||||||
if (is_null($teamId) && auth()->check() && auth()->user()->currentTeam()) {
|
if (is_null($teamId) && auth()->check() && auth()->user()->currentTeam()) {
|
||||||
$teamId = auth()->user()->currentTeam()->id;
|
$teamId = auth()->user()->currentTeam()->id;
|
||||||
}
|
}
|
||||||
$this->teamId = $teamId;
|
$this->teamId = $teamId;
|
||||||
|
$this->activityId = $activityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function broadcastOn(): array
|
public function broadcastOn(): array
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,12 @@
|
||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Actions\Proxy\StartProxy;
|
use App\Actions\Proxy\GetProxyConfiguration;
|
||||||
use App\Actions\Proxy\StopProxy;
|
use App\Actions\Proxy\SaveProxyConfiguration;
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
|
use App\Events\ProxyStatusChangedUI;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Services\ProxyDashboardCacheService;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
|
@ -19,11 +22,13 @@ class RestartProxyJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
|
||||||
public $tries = 1;
|
public $tries = 1;
|
||||||
|
|
||||||
public $timeout = 60;
|
public $timeout = 120;
|
||||||
|
|
||||||
|
public ?int $activity_id = null;
|
||||||
|
|
||||||
public function middleware(): array
|
public function middleware(): array
|
||||||
{
|
{
|
||||||
return [(new WithoutOverlapping('restart-proxy-'.$this->server->uuid))->expireAfter(60)->dontRelease()];
|
return [(new WithoutOverlapping('restart-proxy-'.$this->server->uuid))->expireAfter(120)->dontRelease()];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server) {}
|
||||||
|
|
@ -31,15 +36,125 @@ public function __construct(public Server $server) {}
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
StopProxy::run($this->server, restarting: true);
|
// Set status to restarting
|
||||||
|
$this->server->proxy->status = 'restarting';
|
||||||
$this->server->proxy->force_stop = false;
|
$this->server->proxy->force_stop = false;
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
|
|
||||||
StartProxy::run($this->server, force: true, restarting: true);
|
// Build combined stop + start commands for a single activity
|
||||||
|
$commands = $this->buildRestartCommands();
|
||||||
|
|
||||||
|
// Create activity and dispatch immediately - returns Activity right away
|
||||||
|
// The remote_process runs asynchronously, so UI gets activity ID instantly
|
||||||
|
$activity = remote_process(
|
||||||
|
$commands,
|
||||||
|
$this->server,
|
||||||
|
callEventOnFinish: 'ProxyStatusChanged',
|
||||||
|
callEventData: $this->server->id
|
||||||
|
);
|
||||||
|
|
||||||
|
// Store activity ID and notify UI immediately with it
|
||||||
|
$this->activity_id = $activity->id;
|
||||||
|
ProxyStatusChangedUI::dispatch($this->server->team_id, $this->activity_id);
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
// Set error status
|
||||||
|
$this->server->proxy->status = 'error';
|
||||||
|
$this->server->save();
|
||||||
|
|
||||||
|
// Notify UI of error
|
||||||
|
ProxyStatusChangedUI::dispatch($this->server->team_id);
|
||||||
|
|
||||||
|
// Clear dashboard cache on error
|
||||||
|
ProxyDashboardCacheService::clearCache($this->server);
|
||||||
|
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build combined stop + start commands for proxy restart.
|
||||||
|
* This creates a single command sequence that shows all logs in one activity.
|
||||||
|
*/
|
||||||
|
private function buildRestartCommands(): array
|
||||||
|
{
|
||||||
|
$proxyType = $this->server->proxyType();
|
||||||
|
$containerName = $this->server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
|
||||||
|
$proxy_path = $this->server->proxyPath();
|
||||||
|
$stopTimeout = 30;
|
||||||
|
|
||||||
|
// Get proxy configuration
|
||||||
|
$configuration = GetProxyConfiguration::run($this->server);
|
||||||
|
if (! $configuration) {
|
||||||
|
throw new \Exception('Configuration is not synced');
|
||||||
|
}
|
||||||
|
SaveProxyConfiguration::run($this->server, $configuration);
|
||||||
|
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||||
|
$this->server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value();
|
||||||
|
$this->server->save();
|
||||||
|
|
||||||
|
$commands = collect([]);
|
||||||
|
|
||||||
|
// === STOP PHASE ===
|
||||||
|
$commands = $commands->merge([
|
||||||
|
"echo 'Stopping proxy...'",
|
||||||
|
"docker stop -t=$stopTimeout $containerName 2>/dev/null || true",
|
||||||
|
"docker rm -f $containerName 2>/dev/null || true",
|
||||||
|
'# Wait for container to be fully removed',
|
||||||
|
'for i in {1..15}; do',
|
||||||
|
" if ! docker ps -a --format \"{{.Names}}\" | grep -q \"^$containerName$\"; then",
|
||||||
|
" echo 'Container removed successfully.'",
|
||||||
|
' break',
|
||||||
|
' fi',
|
||||||
|
' echo "Waiting for container to be removed... ($i/15)"',
|
||||||
|
' sleep 1',
|
||||||
|
' # Force remove on each iteration in case it got stuck',
|
||||||
|
" docker rm -f $containerName 2>/dev/null || true",
|
||||||
|
'done',
|
||||||
|
'# Final verification and force cleanup',
|
||||||
|
"if docker ps -a --format \"{{.Names}}\" | grep -q \"^$containerName$\"; then",
|
||||||
|
" echo 'Container still exists after wait, forcing removal...'",
|
||||||
|
" docker rm -f $containerName 2>/dev/null || true",
|
||||||
|
' sleep 2',
|
||||||
|
'fi',
|
||||||
|
"echo 'Proxy stopped successfully.'",
|
||||||
|
]);
|
||||||
|
|
||||||
|
// === START PHASE ===
|
||||||
|
if ($this->server->isSwarmManager()) {
|
||||||
|
$commands = $commands->merge([
|
||||||
|
"echo 'Starting proxy (Swarm mode)...'",
|
||||||
|
"mkdir -p $proxy_path/dynamic",
|
||||||
|
"cd $proxy_path",
|
||||||
|
"echo 'Creating required Docker Compose file.'",
|
||||||
|
"echo 'Starting coolify-proxy.'",
|
||||||
|
'docker stack deploy --detach=true -c docker-compose.yml coolify-proxy',
|
||||||
|
"echo 'Successfully started coolify-proxy.'",
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
if (isDev() && $proxyType === ProxyTypes::CADDY->value) {
|
||||||
|
$proxy_path = '/data/coolify/proxy/caddy';
|
||||||
|
}
|
||||||
|
$caddyfile = 'import /dynamic/*.caddy';
|
||||||
|
$commands = $commands->merge([
|
||||||
|
"echo 'Starting proxy...'",
|
||||||
|
"mkdir -p $proxy_path/dynamic",
|
||||||
|
"cd $proxy_path",
|
||||||
|
"echo '$caddyfile' > $proxy_path/dynamic/Caddyfile",
|
||||||
|
"echo 'Creating required Docker Compose file.'",
|
||||||
|
"echo 'Pulling docker image.'",
|
||||||
|
'docker compose pull',
|
||||||
|
]);
|
||||||
|
// Ensure required networks exist BEFORE docker compose up
|
||||||
|
$commands = $commands->merge(ensureProxyNetworksExist($this->server));
|
||||||
|
$commands = $commands->merge([
|
||||||
|
"echo 'Starting coolify-proxy.'",
|
||||||
|
'docker compose up -d --wait --remove-orphans',
|
||||||
|
"echo 'Successfully started coolify-proxy.'",
|
||||||
|
]);
|
||||||
|
$commands = $commands->merge(connectProxyToNetworks($this->server));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $commands->toArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
use App\Actions\Proxy\StartProxy;
|
use App\Actions\Proxy\StartProxy;
|
||||||
use App\Actions\Proxy\StopProxy;
|
use App\Actions\Proxy\StopProxy;
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProxyTypes;
|
||||||
|
use App\Jobs\RestartProxyJob;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Services\ProxyDashboardCacheService;
|
use App\Services\ProxyDashboardCacheService;
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
|
|
@ -27,6 +28,10 @@ class Navbar extends Component
|
||||||
|
|
||||||
public ?string $proxyStatus = 'unknown';
|
public ?string $proxyStatus = 'unknown';
|
||||||
|
|
||||||
|
public ?string $lastNotifiedStatus = null;
|
||||||
|
|
||||||
|
public bool $restartInitiated = false;
|
||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
$teamId = auth()->user()->currentTeam()->id;
|
$teamId = auth()->user()->currentTeam()->id;
|
||||||
|
|
@ -61,14 +66,19 @@ public function restart()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->authorize('manageProxy', $this->server);
|
$this->authorize('manageProxy', $this->server);
|
||||||
StopProxy::run($this->server, restarting: true);
|
|
||||||
|
|
||||||
$this->server->proxy->force_stop = false;
|
// Prevent duplicate restart calls
|
||||||
$this->server->save();
|
if ($this->restartInitiated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->restartInitiated = true;
|
||||||
|
|
||||||
|
// Always use background job for all servers
|
||||||
|
RestartProxyJob::dispatch($this->server);
|
||||||
|
|
||||||
$activity = StartProxy::run($this->server, force: true, restarting: true);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
$this->restartInitiated = false;
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -122,12 +132,27 @@ public function checkProxyStatus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function showNotification()
|
public function showNotification($event = null)
|
||||||
{
|
{
|
||||||
$previousStatus = $this->proxyStatus;
|
$previousStatus = $this->proxyStatus;
|
||||||
$this->server->refresh();
|
$this->server->refresh();
|
||||||
$this->proxyStatus = $this->server->proxy->status ?? 'unknown';
|
$this->proxyStatus = $this->server->proxy->status ?? 'unknown';
|
||||||
|
|
||||||
|
// If event contains activityId, open activity monitor
|
||||||
|
if ($event && isset($event['activityId'])) {
|
||||||
|
$this->dispatch('activityMonitor', $event['activityId']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset restart flag when proxy reaches a stable state
|
||||||
|
if (in_array($this->proxyStatus, ['running', 'exited', 'error'])) {
|
||||||
|
$this->restartInitiated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip notification if we already notified about this status (prevents duplicates)
|
||||||
|
if ($this->lastNotifiedStatus === $this->proxyStatus) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch ($this->proxyStatus) {
|
switch ($this->proxyStatus) {
|
||||||
case 'running':
|
case 'running':
|
||||||
$this->loadProxyConfiguration();
|
$this->loadProxyConfiguration();
|
||||||
|
|
@ -135,6 +160,7 @@ public function showNotification()
|
||||||
// Don't show during normal start/restart flows (starting, restarting, stopping)
|
// Don't show during normal start/restart flows (starting, restarting, stopping)
|
||||||
if (in_array($previousStatus, ['exited', 'stopped', 'unknown', null])) {
|
if (in_array($previousStatus, ['exited', 'stopped', 'unknown', null])) {
|
||||||
$this->dispatch('success', 'Proxy is running.');
|
$this->dispatch('success', 'Proxy is running.');
|
||||||
|
$this->lastNotifiedStatus = $this->proxyStatus;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'exited':
|
case 'exited':
|
||||||
|
|
@ -142,19 +168,30 @@ public function showNotification()
|
||||||
// Don't show during normal stop/restart flows (stopping, restarting)
|
// Don't show during normal stop/restart flows (stopping, restarting)
|
||||||
if (in_array($previousStatus, ['running'])) {
|
if (in_array($previousStatus, ['running'])) {
|
||||||
$this->dispatch('info', 'Proxy has exited.');
|
$this->dispatch('info', 'Proxy has exited.');
|
||||||
|
$this->lastNotifiedStatus = $this->proxyStatus;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'stopping':
|
case 'stopping':
|
||||||
$this->dispatch('info', 'Proxy is stopping.');
|
// $this->dispatch('info', 'Proxy is stopping.');
|
||||||
|
$this->lastNotifiedStatus = $this->proxyStatus;
|
||||||
break;
|
break;
|
||||||
case 'starting':
|
case 'starting':
|
||||||
$this->dispatch('info', 'Proxy is starting.');
|
// $this->dispatch('info', 'Proxy is starting.');
|
||||||
|
$this->lastNotifiedStatus = $this->proxyStatus;
|
||||||
|
break;
|
||||||
|
case 'restarting':
|
||||||
|
// $this->dispatch('info', 'Proxy is restarting.');
|
||||||
|
$this->lastNotifiedStatus = $this->proxyStatus;
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
$this->dispatch('error', 'Proxy restart failed. Check logs.');
|
||||||
|
$this->lastNotifiedStatus = $this->proxyStatus;
|
||||||
break;
|
break;
|
||||||
case 'unknown':
|
case 'unknown':
|
||||||
$this->dispatch('info', 'Proxy status is unknown.');
|
// Don't notify for unknown status - too noisy
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$this->dispatch('info', 'Proxy status updated.');
|
// Don't notify for other statuses
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,13 @@
|
||||||
<x-slide-over @startproxy.window="slideOverOpen = true" fullScreen closeWithX>
|
<x-slide-over @startproxy.window="slideOverOpen = true" fullScreen closeWithX>
|
||||||
<x-slot:title>Proxy Startup Logs</x-slot:title>
|
<x-slot:title>Proxy Startup Logs</x-slot:title>
|
||||||
<x-slot:content>
|
<x-slot:content>
|
||||||
|
@if ($server->id === 0)
|
||||||
|
<div class="mb-4 p-3 text-sm bg-warning/10 border border-warning/30 rounded-lg text-warning">
|
||||||
|
<span class="font-semibold">Note:</span> This is the localhost server where Coolify runs.
|
||||||
|
During proxy restart, the connection may be temporarily lost.
|
||||||
|
If logs stop updating, please refresh the browser after a few minutes.
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
<livewire:activity-monitor header="Logs" fullHeight />
|
<livewire:activity-monitor header="Logs" fullHeight />
|
||||||
</x-slot:content>
|
</x-slot:content>
|
||||||
</x-slide-over>
|
</x-slide-over>
|
||||||
|
|
@ -174,6 +181,7 @@ class="flex items-center gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$wire.$on('restartEvent', () => {
|
$wire.$on('restartEvent', () => {
|
||||||
|
if ($wire.restartInitiated) return;
|
||||||
window.dispatchEvent(new CustomEvent('startproxy'))
|
window.dispatchEvent(new CustomEvent('startproxy'))
|
||||||
$wire.$call('restart');
|
$wire.$call('restart');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
139
tests/Feature/Proxy/RestartProxyTest.php
Normal file
139
tests/Feature/Proxy/RestartProxyTest.php
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Proxy;
|
||||||
|
|
||||||
|
use App\Jobs\RestartProxyJob;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Team;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Queue;
|
||||||
|
use Livewire\Livewire;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class RestartProxyTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
protected User $user;
|
||||||
|
|
||||||
|
protected Team $team;
|
||||||
|
|
||||||
|
protected Server $server;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
// Create test user and team
|
||||||
|
$this->user = User::factory()->create();
|
||||||
|
$this->team = Team::factory()->create(['name' => 'Test Team']);
|
||||||
|
$this->user->teams()->attach($this->team);
|
||||||
|
|
||||||
|
// Create test server
|
||||||
|
$this->server = Server::factory()->create([
|
||||||
|
'team_id' => $this->team->id,
|
||||||
|
'name' => 'Test Server',
|
||||||
|
'ip' => '192.168.1.100',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Authenticate user
|
||||||
|
$this->actingAs($this->user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_restart_dispatches_job_for_all_servers()
|
||||||
|
{
|
||||||
|
Queue::fake();
|
||||||
|
|
||||||
|
Livewire::test('server.navbar', ['server' => $this->server])
|
||||||
|
->call('restart');
|
||||||
|
|
||||||
|
// Assert job was dispatched
|
||||||
|
Queue::assertPushed(RestartProxyJob::class, function ($job) {
|
||||||
|
return $job->server->id === $this->server->id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_restart_dispatches_job_for_localhost_server()
|
||||||
|
{
|
||||||
|
Queue::fake();
|
||||||
|
|
||||||
|
// Create localhost server (id = 0)
|
||||||
|
$localhostServer = Server::factory()->create([
|
||||||
|
'id' => 0,
|
||||||
|
'team_id' => $this->team->id,
|
||||||
|
'name' => 'Localhost',
|
||||||
|
'ip' => 'host.docker.internal',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Livewire::test('server.navbar', ['server' => $localhostServer])
|
||||||
|
->call('restart');
|
||||||
|
|
||||||
|
// Assert job was dispatched
|
||||||
|
Queue::assertPushed(RestartProxyJob::class, function ($job) use ($localhostServer) {
|
||||||
|
return $job->server->id === $localhostServer->id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_restart_shows_info_message()
|
||||||
|
{
|
||||||
|
Queue::fake();
|
||||||
|
|
||||||
|
Livewire::test('server.navbar', ['server' => $this->server])
|
||||||
|
->call('restart')
|
||||||
|
->assertDispatched('info', 'Proxy restart initiated. Monitor progress in activity logs.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_unauthorized_user_cannot_restart_proxy()
|
||||||
|
{
|
||||||
|
Queue::fake();
|
||||||
|
|
||||||
|
// Create another user without access
|
||||||
|
$unauthorizedUser = User::factory()->create();
|
||||||
|
$this->actingAs($unauthorizedUser);
|
||||||
|
|
||||||
|
Livewire::test('server.navbar', ['server' => $this->server])
|
||||||
|
->call('restart')
|
||||||
|
->assertForbidden();
|
||||||
|
|
||||||
|
// Assert job was NOT dispatched
|
||||||
|
Queue::assertNotPushed(RestartProxyJob::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_restart_prevents_concurrent_jobs_via_without_overlapping()
|
||||||
|
{
|
||||||
|
Queue::fake();
|
||||||
|
|
||||||
|
// Dispatch job twice
|
||||||
|
Livewire::test('server.navbar', ['server' => $this->server])
|
||||||
|
->call('restart');
|
||||||
|
|
||||||
|
Livewire::test('server.navbar', ['server' => $this->server])
|
||||||
|
->call('restart');
|
||||||
|
|
||||||
|
// Assert job was pushed twice (WithoutOverlapping middleware will handle deduplication)
|
||||||
|
Queue::assertPushed(RestartProxyJob::class, 2);
|
||||||
|
|
||||||
|
// Get the jobs
|
||||||
|
$jobs = Queue::pushed(RestartProxyJob::class);
|
||||||
|
|
||||||
|
// Verify both jobs have WithoutOverlapping middleware
|
||||||
|
foreach ($jobs as $job) {
|
||||||
|
$middleware = $job['job']->middleware();
|
||||||
|
$this->assertCount(1, $middleware);
|
||||||
|
$this->assertInstanceOf(\Illuminate\Queue\Middleware\WithoutOverlapping::class, $middleware[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_restart_uses_server_team_id()
|
||||||
|
{
|
||||||
|
Queue::fake();
|
||||||
|
|
||||||
|
Livewire::test('server.navbar', ['server' => $this->server])
|
||||||
|
->call('restart');
|
||||||
|
|
||||||
|
Queue::assertPushed(RestartProxyJob::class, function ($job) {
|
||||||
|
return $job->server->team_id === $this->team->id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
58
tests/Unit/Jobs/RestartProxyJobTest.php
Normal file
58
tests/Unit/Jobs/RestartProxyJobTest.php
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Unit\Jobs;
|
||||||
|
|
||||||
|
use App\Jobs\RestartProxyJob;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
|
use Mockery;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for RestartProxyJob.
|
||||||
|
*
|
||||||
|
* These tests focus on testing the job's middleware configuration and constructor.
|
||||||
|
* Full integration tests for the job's handle() method are in tests/Feature/Proxy/
|
||||||
|
* because they require database and complex mocking of SchemalessAttributes.
|
||||||
|
*/
|
||||||
|
class RestartProxyJobTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function tearDown(): void
|
||||||
|
{
|
||||||
|
Mockery::close();
|
||||||
|
parent::tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_job_has_without_overlapping_middleware()
|
||||||
|
{
|
||||||
|
$server = Mockery::mock(Server::class);
|
||||||
|
$server->shouldReceive('getSchemalessAttributes')->andReturn([]);
|
||||||
|
$server->shouldReceive('getAttribute')->with('uuid')->andReturn('test-uuid');
|
||||||
|
|
||||||
|
$job = new RestartProxyJob($server);
|
||||||
|
$middleware = $job->middleware();
|
||||||
|
|
||||||
|
$this->assertCount(1, $middleware);
|
||||||
|
$this->assertInstanceOf(WithoutOverlapping::class, $middleware[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_job_has_correct_configuration()
|
||||||
|
{
|
||||||
|
$server = Mockery::mock(Server::class);
|
||||||
|
|
||||||
|
$job = new RestartProxyJob($server);
|
||||||
|
|
||||||
|
$this->assertEquals(1, $job->tries);
|
||||||
|
$this->assertEquals(120, $job->timeout);
|
||||||
|
$this->assertNull($job->activity_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_job_stores_server()
|
||||||
|
{
|
||||||
|
$server = Mockery::mock(Server::class);
|
||||||
|
|
||||||
|
$job = new RestartProxyJob($server);
|
||||||
|
|
||||||
|
$this->assertSame($server, $job->server);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue