mirror of
https://github.com/tiennm99/coolify.git
synced 2026-08-20 06:23:23 +00:00
159 lines
5.7 KiB
PHP
159 lines
5.7 KiB
PHP
<?php
|
|
|
|
use App\Models\Application;
|
|
use App\Models\Environment;
|
|
use App\Models\InstanceSettings;
|
|
use App\Models\PrivateKey;
|
|
use App\Models\Project;
|
|
use App\Models\Server;
|
|
use App\Models\StandaloneDocker;
|
|
use App\Models\Team;
|
|
use App\Services\ServerTransfer\ServerTransferClaimer;
|
|
use App\Services\ServerTransfer\ServerTransferMigrator;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Tests\TestCase;
|
|
|
|
uses(TestCase::class, RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
InstanceSettings::forceCreate(['id' => 0, 'is_api_enabled' => true, 'fqdn' => 'https://coolify-a.test']);
|
|
|
|
$this->team = Team::factory()->create();
|
|
$this->privateKey = PrivateKey::factory()->create(['team_id' => $this->team->id]);
|
|
$this->server = Server::factory()->create([
|
|
'team_id' => $this->team->id,
|
|
'private_key_id' => $this->privateKey->id,
|
|
'ip' => '10.88.0.10',
|
|
'name' => 'migrate-me',
|
|
]);
|
|
$destination = StandaloneDocker::where('server_id', $this->server->id)->firstOrFail();
|
|
$project = Project::factory()->create(['team_id' => $this->team->id]);
|
|
$environment = $project->environments()->first()
|
|
?? Environment::factory()->create(['project_id' => $project->id]);
|
|
Application::factory()->create([
|
|
'environment_id' => $environment->id,
|
|
'destination_id' => $destination->id,
|
|
'destination_type' => $destination->getMorphClass(),
|
|
'name' => 'app-on-server',
|
|
'ports_exposes' => '3000',
|
|
]);
|
|
});
|
|
|
|
test('migrate exports imports via http and completes locally', function () {
|
|
Http::fake([
|
|
'http://target.test/api/v1/servers/import' => Http::response([
|
|
'dry_run' => false,
|
|
'server_uuid' => $this->server->uuid,
|
|
'claimed' => true,
|
|
'export_id' => 'remote-export',
|
|
'created' => ['applications' => 1],
|
|
'warnings' => [],
|
|
], 201),
|
|
]);
|
|
|
|
$result = app(ServerTransferMigrator::class)->migrate(
|
|
server: $this->server,
|
|
targetUrl: 'http://target.test',
|
|
targetToken: 'target-token-xyz',
|
|
writeRemote: false,
|
|
);
|
|
|
|
expect($result['server_uuid'])->toBe($this->server->uuid)
|
|
->and($result['target_url'])->toBe('http://target.test')
|
|
->and($result['import']['claimed'])->toBeTrue()
|
|
->and($result['message'])->toContain('migrated');
|
|
|
|
$this->server->refresh();
|
|
expect(data_get($this->server->server_metadata, 'transfer.status'))->toBe('transferred')
|
|
->and((bool) $this->server->settings->force_disabled)->toBeTrue();
|
|
|
|
Http::assertSent(function ($request) {
|
|
return $request->url() === 'http://target.test/api/v1/servers/import'
|
|
&& $request->hasHeader('Authorization', 'Bearer target-token-xyz')
|
|
&& data_get($request->data(), 'claim') === true
|
|
&& data_get($request->data(), 'bundle.server.uuid') === $this->server->uuid;
|
|
});
|
|
});
|
|
|
|
test('migrate rewrites localhost target when running in docker style env', function () {
|
|
// Simulate container: create a temp marker if missing is hard; instead assert host rewrite helper via migrate call
|
|
// with Http fake matching host.docker.internal when /.dockerenv exists — skip if not in docker.
|
|
if (! file_exists('/.dockerenv') && ! is_file('/run/.containerenv')) {
|
|
expect(true)->toBeTrue();
|
|
|
|
return;
|
|
}
|
|
|
|
Http::fake([
|
|
'http://host.docker.internal:8001/api/v1/servers/import' => Http::response([
|
|
'dry_run' => false,
|
|
'server_uuid' => $this->server->uuid,
|
|
'claimed' => true,
|
|
'warnings' => [],
|
|
], 201),
|
|
]);
|
|
|
|
app(ServerTransferMigrator::class)->migrate(
|
|
$this->server,
|
|
'http://localhost:8001',
|
|
'token',
|
|
);
|
|
|
|
Http::assertSent(fn ($request) => str_contains($request->url(), 'host.docker.internal:8001'));
|
|
});
|
|
|
|
test('migrate fails clearly when target is unreachable', function () {
|
|
Http::fake([
|
|
'http://down.test/*' => Http::failedConnection(),
|
|
]);
|
|
|
|
expect(fn () => app(ServerTransferMigrator::class)->migrate(
|
|
$this->server,
|
|
'http://down.test',
|
|
'token',
|
|
))->toThrow(RuntimeException::class, 'Could not reach target');
|
|
});
|
|
|
|
test('migrate fails when target returns error', function () {
|
|
Http::fake([
|
|
'http://target.test/api/v1/servers/import' => Http::response([
|
|
'message' => 'A server with IP/domain already exists',
|
|
], 422),
|
|
]);
|
|
|
|
expect(fn () => app(ServerTransferMigrator::class)->migrate(
|
|
$this->server,
|
|
'http://target.test',
|
|
'token',
|
|
))->toThrow(RuntimeException::class, 'Target import failed');
|
|
|
|
// Source must remain unmanaged-away only after successful remote import+complete.
|
|
$this->server->refresh();
|
|
expect(data_get($this->server->server_metadata, 'transfer.status'))->not->toBe('transferred')
|
|
->and((bool) $this->server->settings->force_disabled)->toBeFalse();
|
|
});
|
|
|
|
test('migrate surfaces recovery guidance when complete fails after successful remote import', function () {
|
|
Http::fake([
|
|
'http://target.test/api/v1/servers/import' => Http::response([
|
|
'dry_run' => false,
|
|
'server_uuid' => $this->server->uuid,
|
|
'claimed' => true,
|
|
'warnings' => [],
|
|
], 201),
|
|
]);
|
|
|
|
$claimer = Mockery::mock(ServerTransferClaimer::class)->makePartial();
|
|
$claimer->shouldReceive('markTransferred')
|
|
->once()
|
|
->andThrow(new RuntimeException('simulated complete failure'));
|
|
app()->instance(ServerTransferClaimer::class, $claimer);
|
|
|
|
expect(fn () => app(ServerTransferMigrator::class)->migrate(
|
|
$this->server,
|
|
'http://target.test',
|
|
'token',
|
|
))->toThrow(RuntimeException::class, 'Retry complete');
|
|
});
|