BLADE); expect($html) ->toContain('Backup is not configured') ->toContain('Configure backup'); }); test('empty component renders the contents slot for backward compatibility', function () { $html = Blade::render(<<<'BLADE' BLADE); expect($html) ->toContain('Metrics are disabled') ->toContain('Enable metrics'); }); test('empty component renders only the contents slot when both action slots are provided', function () { $html = Blade::render(<<<'BLADE' BLADE); expect($html) ->toContain('From contents') ->not->toContain('From actions'); }); test('instance backup settings show a configure button when backup is not set up', function () { $rootTeam = Team::find(0) ?? Team::factory()->create(['id' => 0]); $server = Server::factory()->create([ 'id' => 0, 'team_id' => $rootTeam->id, 'ip' => '127.0.0.1', ]); $server->settings()->update([ 'is_reachable' => true, 'is_usable' => true, 'force_disabled' => false, ]); InstanceSettings::forceCreate(['id' => 0]); Once::flush(); $user = User::factory()->create(); $rootTeam->members()->attach($user->id, ['role' => 'admin']); $this->actingAs($user); session(['currentTeam' => ['id' => $rootTeam->id]]); Livewire::test(SettingsBackup::class) ->assertOk() ->assertSee('Backup is not configured') ->assertSee('Configure backup') ->assertSeeHtml('wire:click="addCoolifyDatabase"'); }); function settingsBackupInstanceDatabaseWithoutSchedule(): StandalonePostgresql { Server::flushIdentityMap(); $rootTeam = Team::find(0) ?? Team::factory()->create(['id' => 0]); $server = Server::factory()->create([ 'id' => 0, 'team_id' => $rootTeam->id, 'ip' => '127.0.0.1', ]); $server->settings()->update([ 'is_reachable' => true, 'is_usable' => true, 'force_disabled' => false, ]); StandaloneDocker::query()->where('server_id', $server->id)->firstOrFail()->forceFill(['id' => 0])->save(); InstanceSettings::forceCreate(['id' => 0]); Once::flush(); $database = new StandalonePostgresql; $database->forceFill([ 'id' => 0, 'name' => 'coolify-db', 'description' => 'Coolify database', 'postgres_user' => 'coolify', 'postgres_password' => 'password', 'postgres_db' => 'coolify', 'status' => 'running', 'destination_type' => StandaloneDocker::class, 'destination_id' => 0, ]); $database->save(); $user = User::factory()->create(); $rootTeam->members()->attach($user->id, ['role' => 'admin']); test()->actingAs($user); session(['currentTeam' => ['id' => $rootTeam->id]]); return $database; } test('instance backup settings render when the instance database has no backup schedule', function () { settingsBackupInstanceDatabaseWithoutSchedule(); Livewire::test(SettingsBackup::class) ->assertOk() ->assertSee('Backup is not configured') ->assertSee('Configure backup'); }); test('configure backup creates only the missing schedule for an existing instance database', function () { $database = settingsBackupInstanceDatabaseWithoutSchedule(); $otherTeam = Team::factory()->create(); auth()->user()->teams()->attach($otherTeam->id, ['role' => 'owner']); session(['currentTeam' => ['id' => $otherTeam->id]]); Process::fake(); Livewire::test(SettingsBackup::class) ->call('addCoolifyDatabase') ->assertHasNoErrors() ->assertSee('Instance database'); Process::assertNothingRan(); $backup = $database->scheduledBackups()->sole(); expect(StandalonePostgresql::query()->where('name', 'coolify-db')->count())->toBe(1) ->and($backup->id)->not->toBe(0) ->and($backup->team_id)->toBe(0) ->and($backup->enabled)->toBeTruthy() ->and($backup->frequency)->toBe('0 0 * * *') ->and(ScheduledDatabaseBackup::query()->count())->toBe(1); }); /** * A team database that is also named coolify-db, but is not the instance database (id 0). */ function settingsBackupTenantDatabaseNamedCoolifyDb(): StandalonePostgresql { $tenantTeam = Team::factory()->create(); $tenantServer = Server::factory()->create(['team_id' => $tenantTeam->id]); $tenantDestination = StandaloneDocker::query()->where('server_id', $tenantServer->id)->firstOrFail(); $database = new StandalonePostgresql; $database->forceFill([ 'name' => 'coolify-db', 'postgres_user' => 'tenant', 'postgres_password' => 'tenant-secret', 'postgres_db' => 'tenant', 'status' => 'running', 'destination_type' => StandaloneDocker::class, 'destination_id' => $tenantDestination->id, ]); $database->save(); return $database; } test('instance backup settings ignore a team database named coolify-db', function () { settingsBackupInstanceDatabaseWithoutSchedule()->delete(); $tenantDatabase = settingsBackupTenantDatabaseNamedCoolifyDb(); Livewire::test(SettingsBackup::class) ->assertOk() ->assertSet('database', null) ->assertSee('Backup is not configured') ->assertDontSee('tenant-secret'); expect($tenantDatabase->fresh()->scheduledBackups()->exists())->toBeFalse(); }); test('configure backup creates the instance database instead of using a team database named coolify-db', function () { settingsBackupInstanceDatabaseWithoutSchedule()->forceDelete(); $tenantDatabase = settingsBackupTenantDatabaseNamedCoolifyDb(); Storage::fake('ssh-keys'); Storage::fake('ssh-mux'); Server::query()->whereKey(0)->update(['private_key_id' => PrivateKey::factory()->create(['team_id' => 0])->id]); Server::flushIdentityMap(); fakeRemoteProcesses([ '*docker inspect coolify-db*' => Process::result(output: json_encode([[ 'Config' => ['Env' => ['POSTGRES_USER=coolify', 'POSTGRES_PASSWORD=instance-secret', 'POSTGRES_DB=coolify']], ]])), ]); Livewire::test(SettingsBackup::class) ->call('addCoolifyDatabase') ->assertHasNoErrors(); $instanceDatabase = StandalonePostgresql::query()->findOrFail(0); expect($instanceDatabase->postgres_password)->toBe('instance-secret') ->and($instanceDatabase->scheduledBackups()->sole()->team_id)->toBe(0) ->and($tenantDatabase->fresh()->scheduledBackups()->exists())->toBeFalse() ->and($tenantDatabase->fresh()->postgres_password)->toBe('tenant-secret'); }); test('saving instance backup settings only persists the description', function () { $database = settingsBackupInstanceDatabaseWithoutSchedule(); $database->forceFill(['postgres_password' => 'instance-db-secret'])->save(); $database->scheduledBackups()->create([ 'enabled' => true, 'save_s3' => false, 'frequency' => '0 0 * * *', 'team_id' => 0, ]); Livewire::test(SettingsBackup::class) ->assertSee('Instance database') ->assertSee('coolify') ->assertDontSeeHtml('wire:model=postgres_user') ->assertDontSeeHtml('wire:model=postgres_password') ->assertDontSee('instance-db-secret', stripInitialData: false) ->set('description', 'Updated description') ->call('submit') ->assertHasNoErrors(); $database->refresh(); expect($database->description)->toBe('Updated description') ->and($database->name)->toBe('coolify-db') ->and($database->postgres_user)->toBe('coolify') ->and($database->postgres_password)->toBe('instance-db-secret'); });