coolify/tests/Unit/ServerPlaceholderIpTest.php
Andras Bacsai c8a332a3bc fix(servers): isolate cloud status checks from SSH checks
Track provider state independently, skip SSH work for placeholder IPs, and clean up failed cloud server provisioning.
2026-07-11 22:52:10 +02:00

125 lines
4.4 KiB
PHP

<?php
use App\Jobs\CleanupOrphanedPreviewContainersJob;
use App\Jobs\ScheduledJobManager;
use App\Models\PrivateKey;
use App\Models\Server;
use App\Models\Team;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
function createServerForPlaceholderIpTest(string $ip): Server
{
$team = Team::create([
'name' => 'Test Team',
'personal_team' => false,
]);
$privateKey = PrivateKey::factory()->create(['team_id' => $team->id]);
return Server::factory()->create([
'team_id' => $team->id,
'private_key_id' => $privateKey->id,
'ip' => $ip,
]);
}
it('detects placeholder IPs', function () {
$server = createServerForPlaceholderIpTest(Server::PLACEHOLDER_IP);
foreach ([Server::PLACEHOLDER_IP, '0.0.0.0', '::'] as $ip) {
$server->update(['ip' => $ip]);
expect($server->fresh()->hasPlaceholderIp())->toBeTrue();
}
});
it('does not treat a real IP as a placeholder', function () {
$server = createServerForPlaceholderIpTest('203.0.113.5');
expect($server->hasPlaceholderIp())->toBeFalse();
});
it('backfills a placeholder IP with the real address', function () {
$server = createServerForPlaceholderIpTest(Server::PLACEHOLDER_IP);
expect($server->backfillPlaceholderIp('203.0.113.5'))->toBeTrue();
expect($server->fresh()->ip)->toBe('203.0.113.5');
});
it('does not overwrite a real IP when backfilling', function () {
$server = createServerForPlaceholderIpTest('203.0.113.5');
expect($server->backfillPlaceholderIp('198.51.100.9'))->toBeFalse();
expect($server->fresh()->ip)->toBe('203.0.113.5');
});
it('ignores a missing IP when backfilling', function () {
$server = createServerForPlaceholderIpTest(Server::PLACEHOLDER_IP);
expect($server->backfillPlaceholderIp(null))->toBeFalse();
expect($server->fresh()->ip)->toBe(Server::PLACEHOLDER_IP);
});
it('does not replace a placeholder with another placeholder', function (string $replacementIp) {
$server = createServerForPlaceholderIpTest(Server::PLACEHOLDER_IP);
expect($server->backfillPlaceholderIp($replacementIp))->toBeFalse();
expect($server->fresh()->ip)->toBe(Server::PLACEHOLDER_IP);
})->with([
'unspecified IPv4 address' => '0.0.0.0',
'unspecified IPv6 address' => '::',
]);
it('does not overwrite an address configured after the placeholder model was loaded', function () {
$staleServer = createServerForPlaceholderIpTest(Server::PLACEHOLDER_IP);
$concurrentServer = Server::query()->findOrFail($staleServer->id);
$concurrentServer->update(['ip' => 'server.internal.example.com']);
expect($staleServer->backfillPlaceholderIp('203.0.113.10'))->toBeFalse()
->and($staleServer->fresh()->ip)->toBe('server.internal.example.com');
});
it('skips servers with a placeholder IP in scheduled jobs', function () {
$server = createServerForPlaceholderIpTest(Server::PLACEHOLDER_IP);
expect($server->skipServer())->toBeTrue();
});
it('excludes every placeholder address from scheduled Docker cleanup', function () {
$realServer = createServerForPlaceholderIpTest('203.0.113.10');
foreach (Server::PLACEHOLDER_IPS as $placeholderIp) {
Server::factory()->create([
'team_id' => $realServer->team_id,
'private_key_id' => $realServer->private_key_id,
'ip' => $placeholderIp,
]);
}
$method = new ReflectionMethod(ScheduledJobManager::class, 'getServersForCleanupQuery');
$servers = $method->invoke(new ScheduledJobManager)->get();
expect($servers->modelKeys())->toBe([$realServer->id]);
});
it('excludes every placeholder address from orphaned preview cleanup', function () {
$realServer = createServerForPlaceholderIpTest('203.0.113.10');
$realServer->settings->update(['is_reachable' => true, 'is_usable' => true]);
foreach (Server::PLACEHOLDER_IPS as $placeholderIp) {
$server = Server::factory()->create([
'team_id' => $realServer->team_id,
'private_key_id' => $realServer->private_key_id,
'ip' => $placeholderIp,
]);
$server->settings->update(['is_reachable' => true, 'is_usable' => true]);
}
$method = new ReflectionMethod(CleanupOrphanedPreviewContainersJob::class, 'getServersToCheck');
$servers = $method->invoke(new CleanupOrphanedPreviewContainersJob);
expect($servers->modelKeys())->toBe([$realServer->id]);
});