create(); $server = Server::factory()->create(['team_id' => $team->id]); // Default notification threshold is 80%. $data = [ 'containers' => [], 'filesystem_usage_root' => ['used_percentage' => 85], ]; $job = new PushServerUpdateJob($server, $data); $job->handle(); Queue::assertPushed(ServerStorageCheckJob::class, function ($job) use ($server) { return $job->server->id === $server->id && $job->percentage === 85; }); }); it('does not dispatch storage check when disk usage is below threshold', function () { $team = Team::factory()->create(); $server = Server::factory()->create(['team_id' => $team->id]); // 45% is well below the default 80% notification threshold — nothing to do. $data = [ 'containers' => [], 'filesystem_usage_root' => ['used_percentage' => 45], ]; $job = new PushServerUpdateJob($server, $data); $job->handle(); Queue::assertNotPushed(ServerStorageCheckJob::class); }); it('clears stale storage cache when disk usage drops below threshold', function () { $team = Team::factory()->create(); $server = Server::factory()->create(['team_id' => $team->id]); $storageCacheKey = 'storage-check:'.$server->id; Cache::put($storageCacheKey, 85, 600); $belowThresholdData = [ 'containers' => [], 'filesystem_usage_root' => ['used_percentage' => 45], ]; $job = new PushServerUpdateJob($server, $belowThresholdData); $job->handle(); Queue::assertNotPushed(ServerStorageCheckJob::class); expect(Cache::missing($storageCacheKey))->toBeTrue(); Queue::fake(); $aboveThresholdData = [ 'containers' => [], 'filesystem_usage_root' => ['used_percentage' => 85], ]; $job = new PushServerUpdateJob($server, $aboveThresholdData); $job->handle(); Queue::assertPushed(ServerStorageCheckJob::class, function ($job) use ($server) { return $job->server->id === $server->id && $job->percentage === 85; }); }); it('does not dispatch storage check when disk percentage is unchanged', function () { $team = Team::factory()->create(); $server = Server::factory()->create(['team_id' => $team->id]); // Simulate a previous push that cached the percentage (above threshold). Cache::put('storage-check:'.$server->id, 85, 600); $data = [ 'containers' => [], 'filesystem_usage_root' => ['used_percentage' => 85], ]; $job = new PushServerUpdateJob($server, $data); $job->handle(); Queue::assertNotPushed(ServerStorageCheckJob::class); }); it('dispatches storage check when disk percentage changes from cached value', function () { $team = Team::factory()->create(); $server = Server::factory()->create(['team_id' => $team->id]); // Simulate a previous push that cached 85% (above threshold). Cache::put('storage-check:'.$server->id, 85, 600); $data = [ 'containers' => [], 'filesystem_usage_root' => ['used_percentage' => 90], ]; $job = new PushServerUpdateJob($server, $data); $job->handle(); Queue::assertPushed(ServerStorageCheckJob::class, function ($job) use ($server) { return $job->server->id === $server->id && $job->percentage === 90; }); }); it('does not dispatch proxy network work when the proxy is running', function () { $team = Team::factory()->create(); $server = Server::factory()->create(['team_id' => $team->id]); $server->settings->update(['is_reachable' => true, 'is_usable' => true]); $data = [ 'containers' => [ [ 'name' => 'coolify-proxy', 'state' => 'running', 'health_status' => 'healthy', 'labels' => ['coolify.managed' => true], ], ], 'filesystem_usage_root' => ['used_percentage' => 10], ]; Event::fake([ServiceChecked::class]); (new PushServerUpdateJob($server, $data))->handle(); Queue::assertNothingPushed(); }); it('marks a stale proxy status as running when sentinel reports a running proxy', function () { $team = Team::factory()->create(); $server = Server::factory()->create(['team_id' => $team->id]); $server->settings->update(['is_reachable' => true, 'is_usable' => true]); $server->proxy->set('type', 'TRAEFIK'); $server->proxy->set('status', 'exited'); $server->save(); $data = [ 'containers' => [ [ 'name' => 'coolify-proxy', 'state' => 'running', 'health_status' => 'healthy', 'labels' => ['coolify.managed' => true], ], ], 'filesystem_usage_root' => ['used_percentage' => 10], ]; Event::fake([ServiceChecked::class]); (new PushServerUpdateJob($server, $data))->handle(); expect($server->fresh()->proxy->get('status'))->toBe('running'); }); it('does not start a second proxy while a proxy restart runs', function () { $team = Team::factory()->create(); $server = Server::factory()->create(['team_id' => $team->id]); $server->settings->update(['is_reachable' => true, 'is_usable' => true]); $server->proxy->set('type', 'TRAEFIK'); $server->proxy->set('status', 'restarting'); $server->save(); $server->markProxyTransitionStarted(); CheckProxy::shouldNotRun(); StartProxy::shouldNotRun(); Event::fake([ServiceChecked::class]); (new PushServerUpdateJob($server, ['containers' => [], 'filesystem_usage_root' => ['used_percentage' => 10]]))->handle(); expect($server->fresh()->proxy->get('status'))->toBe('restarting'); }); it('starts a missing proxy when no proxy restart runs', function () { $team = Team::factory()->create(); $server = Server::factory()->create(['team_id' => $team->id]); $server->settings->update(['is_reachable' => true, 'is_usable' => true]); $server->proxy->set('type', 'TRAEFIK'); $server->save(); CheckProxy::shouldRun()->once()->andReturn(true); StartProxy::shouldRun()->once(); Event::fake([ServiceChecked::class]); (new PushServerUpdateJob($server, ['containers' => [], 'filesystem_usage_root' => ['used_percentage' => 10]]))->handle(); }); it('ends the proxy restart when the proxy status listener runs', function () { $team = Team::factory()->create(); $server = Server::factory()->create(['team_id' => $team->id]); $server->markProxyTransitionStarted(); Process::fake(); (new ProxyStatusChangedNotification)->handle(new ProxyStatusChanged(ProxyStatusChanged::dataFor($server))); expect($server->isProxyTransitionInProgress())->toBeFalse(); }); it('uses default queue for PushServerUpdateJob', function () { $team = Team::factory()->create(); $server = Server::factory()->create(['team_id' => $team->id]); $job = new PushServerUpdateJob($server, ['containers' => []]); expect($job->queue)->toBeNull(); });