2026-03-29 22:06:45 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
use App\Models\Application;
|
2026-03-31 07:29:36 +00:00
|
|
|
use App\Models\ApplicationPreview;
|
|
|
|
|
use App\Models\ApplicationSetting;
|
2026-03-29 22:06:45 +00:00
|
|
|
use App\Models\Environment;
|
|
|
|
|
use App\Models\LocalPersistentVolume;
|
|
|
|
|
use App\Models\Project;
|
2026-03-31 07:29:36 +00:00
|
|
|
use App\Models\ScheduledTask;
|
2026-03-29 22:06:45 +00:00
|
|
|
use App\Models\Server;
|
|
|
|
|
use App\Models\Team;
|
|
|
|
|
use App\Models\User;
|
2026-03-31 07:29:36 +00:00
|
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
|
|
|
|
|
|
uses(RefreshDatabase::class);
|
2026-03-29 22:06:45 +00:00
|
|
|
|
|
|
|
|
beforeEach(function () {
|
|
|
|
|
$this->user = User::factory()->create();
|
|
|
|
|
$this->team = Team::factory()->create();
|
|
|
|
|
$this->user->teams()->attach($this->team, ['role' => 'owner']);
|
|
|
|
|
|
|
|
|
|
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
|
2026-03-31 07:29:36 +00:00
|
|
|
$this->destination = $this->server->standaloneDockers()->firstOrFail();
|
2026-03-29 22:06:45 +00:00
|
|
|
$this->project = Project::factory()->create(['team_id' => $this->team->id]);
|
|
|
|
|
$this->environment = Environment::factory()->create(['project_id' => $this->project->id]);
|
|
|
|
|
|
|
|
|
|
$this->application = Application::factory()->create([
|
|
|
|
|
'environment_id' => $this->environment->id,
|
|
|
|
|
'destination_id' => $this->destination->id,
|
|
|
|
|
'destination_type' => $this->destination->getMorphClass(),
|
2026-03-31 07:29:36 +00:00
|
|
|
'redirect' => 'both',
|
2026-03-29 22:06:45 +00:00
|
|
|
]);
|
|
|
|
|
|
2026-03-31 11:45:31 +00:00
|
|
|
$this->application->settings->fill([
|
2026-03-31 07:29:36 +00:00
|
|
|
'is_container_label_readonly_enabled' => false,
|
|
|
|
|
])->save();
|
|
|
|
|
|
2026-03-29 22:06:45 +00:00
|
|
|
$this->actingAs($this->user);
|
|
|
|
|
session(['currentTeam' => $this->team]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('cloning application generates new uuid for persistent volumes', function () {
|
|
|
|
|
$volume = LocalPersistentVolume::create([
|
|
|
|
|
'name' => $this->application->uuid.'-data',
|
|
|
|
|
'mount_path' => '/data',
|
|
|
|
|
'resource_id' => $this->application->id,
|
|
|
|
|
'resource_type' => $this->application->getMorphClass(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$originalUuid = $volume->uuid;
|
|
|
|
|
|
|
|
|
|
$newApp = clone_application($this->application, $this->destination, [
|
|
|
|
|
'environment_id' => $this->environment->id,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$clonedVolume = $newApp->persistentStorages()->first();
|
|
|
|
|
|
|
|
|
|
expect($clonedVolume)->not->toBeNull();
|
|
|
|
|
expect($clonedVolume->uuid)->not->toBe($originalUuid);
|
|
|
|
|
expect($clonedVolume->mount_path)->toBe('/data');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('cloning application with multiple persistent volumes generates unique uuids', function () {
|
|
|
|
|
$volume1 = LocalPersistentVolume::create([
|
|
|
|
|
'name' => $this->application->uuid.'-data',
|
|
|
|
|
'mount_path' => '/data',
|
|
|
|
|
'resource_id' => $this->application->id,
|
|
|
|
|
'resource_type' => $this->application->getMorphClass(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$volume2 = LocalPersistentVolume::create([
|
|
|
|
|
'name' => $this->application->uuid.'-config',
|
|
|
|
|
'mount_path' => '/config',
|
|
|
|
|
'resource_id' => $this->application->id,
|
|
|
|
|
'resource_type' => $this->application->getMorphClass(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$newApp = clone_application($this->application, $this->destination, [
|
|
|
|
|
'environment_id' => $this->environment->id,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$clonedVolumes = $newApp->persistentStorages()->get();
|
|
|
|
|
|
|
|
|
|
expect($clonedVolumes)->toHaveCount(2);
|
|
|
|
|
|
|
|
|
|
$clonedUuids = $clonedVolumes->pluck('uuid')->toArray();
|
|
|
|
|
$originalUuids = [$volume1->uuid, $volume2->uuid];
|
|
|
|
|
|
|
|
|
|
// All cloned UUIDs should be unique and different from originals
|
|
|
|
|
expect($clonedUuids)->each->not->toBeIn($originalUuids);
|
|
|
|
|
expect(array_unique($clonedUuids))->toHaveCount(2);
|
|
|
|
|
});
|
2026-03-31 07:29:36 +00:00
|
|
|
|
|
|
|
|
test('cloning application reassigns settings to the cloned application', function () {
|
2026-03-31 11:45:31 +00:00
|
|
|
$this->application->settings->fill([
|
2026-03-31 07:29:36 +00:00
|
|
|
'is_static' => true,
|
|
|
|
|
'is_spa' => true,
|
|
|
|
|
'is_build_server_enabled' => true,
|
|
|
|
|
])->save();
|
|
|
|
|
|
|
|
|
|
$newApp = clone_application($this->application, $this->destination, [
|
|
|
|
|
'environment_id' => $this->environment->id,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$sourceSettingsCount = ApplicationSetting::query()
|
|
|
|
|
->where('application_id', $this->application->id)
|
|
|
|
|
->count();
|
|
|
|
|
$clonedSettings = ApplicationSetting::query()
|
|
|
|
|
->where('application_id', $newApp->id)
|
|
|
|
|
->first();
|
|
|
|
|
|
|
|
|
|
expect($sourceSettingsCount)->toBe(1)
|
|
|
|
|
->and($clonedSettings)->not->toBeNull()
|
|
|
|
|
->and($clonedSettings?->application_id)->toBe($newApp->id)
|
|
|
|
|
->and($clonedSettings?->is_static)->toBeTrue()
|
|
|
|
|
->and($clonedSettings?->is_spa)->toBeTrue()
|
|
|
|
|
->and($clonedSettings?->is_build_server_enabled)->toBeTrue();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('cloning application reassigns scheduled tasks and previews to the cloned application', function () {
|
2026-03-31 11:45:31 +00:00
|
|
|
$scheduledTask = ScheduledTask::create([
|
2026-03-31 07:29:36 +00:00
|
|
|
'uuid' => 'scheduled-task-original',
|
|
|
|
|
'application_id' => $this->application->id,
|
|
|
|
|
'team_id' => $this->team->id,
|
|
|
|
|
'name' => 'nightly-task',
|
|
|
|
|
'command' => 'php artisan schedule:run',
|
|
|
|
|
'frequency' => '* * * * *',
|
|
|
|
|
'container' => 'app',
|
|
|
|
|
'timeout' => 120,
|
|
|
|
|
]);
|
|
|
|
|
|
2026-03-31 11:45:31 +00:00
|
|
|
$preview = ApplicationPreview::create([
|
2026-03-31 07:29:36 +00:00
|
|
|
'uuid' => 'preview-original',
|
|
|
|
|
'application_id' => $this->application->id,
|
|
|
|
|
'pull_request_id' => 123,
|
|
|
|
|
'pull_request_html_url' => 'https://example.com/pull/123',
|
|
|
|
|
'fqdn' => 'https://preview.example.com',
|
|
|
|
|
'status' => 'running',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$newApp = clone_application($this->application, $this->destination, [
|
|
|
|
|
'environment_id' => $this->environment->id,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$clonedTask = ScheduledTask::query()
|
|
|
|
|
->where('application_id', $newApp->id)
|
|
|
|
|
->first();
|
|
|
|
|
$clonedPreview = ApplicationPreview::query()
|
|
|
|
|
->where('application_id', $newApp->id)
|
|
|
|
|
->first();
|
|
|
|
|
|
|
|
|
|
expect($clonedTask)->not->toBeNull()
|
|
|
|
|
->and($clonedTask?->uuid)->not->toBe($scheduledTask->uuid)
|
|
|
|
|
->and($clonedTask?->application_id)->toBe($newApp->id)
|
|
|
|
|
->and($clonedTask?->team_id)->toBe($this->team->id)
|
|
|
|
|
->and($clonedPreview)->not->toBeNull()
|
|
|
|
|
->and($clonedPreview?->uuid)->not->toBe($preview->uuid)
|
|
|
|
|
->and($clonedPreview?->application_id)->toBe($newApp->id)
|
|
|
|
|
->and($clonedPreview?->status)->toBe('exited');
|
|
|
|
|
});
|