diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index 3c9c832d31..652871e89b 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -1017,8 +1017,6 @@ class DatabasesController extends Controller $backupData['databases_to_backup'] = $database->mariadb_database; } elseif ($database->type() === 'standalone-clickhouse') { $backupData['databases_to_backup'] = $database->clickhouse_db; - } elseif ($database->type() === 'standalone-sqlite') { - $backupData['databases_to_backup'] = $database->sqlite_databases; } } diff --git a/app/Livewire/Project/Database/CreateScheduledBackup.php b/app/Livewire/Project/Database/CreateScheduledBackup.php index e113173ffc..96d2ac7aaf 100644 --- a/app/Livewire/Project/Database/CreateScheduledBackup.php +++ b/app/Livewire/Project/Database/CreateScheduledBackup.php @@ -80,8 +80,6 @@ class CreateScheduledBackup extends Component $payload['databases_to_backup'] = $database->mariadb_database; } elseif ($database->type() === 'standalone-clickhouse') { $payload['databases_to_backup'] = $database->clickhouse_db; - } elseif ($database->type() === 'standalone-sqlite') { - $payload['databases_to_backup'] = $database->sqlite_databases; } $databaseBackup = ScheduledDatabaseBackup::create($payload); diff --git a/tests/Feature/Api/DatabaseBackupCreationApiTest.php b/tests/Feature/Api/DatabaseBackupCreationApiTest.php index 0eab7b8fd0..9a5cdc90c9 100644 --- a/tests/Feature/Api/DatabaseBackupCreationApiTest.php +++ b/tests/Feature/Api/DatabaseBackupCreationApiTest.php @@ -130,7 +130,7 @@ describe('POST /api/v1/databases/{uuid}/backups', function () { ->and($backup->database_type)->toBe(StandaloneClickhouse::class); }); - test('defaults sqlite backups to every configured database file', function () { + test('leaves sqlite backup files unset so every configured file is backed up at run time', function () { $database = create_standalone_sqlite($this->environment->id, $this->destination, ['sqlite_databases' => 'app.db,jobs.db']); $response = $this->withHeaders(backupHeaders()) @@ -142,10 +142,24 @@ describe('POST /api/v1/databases/{uuid}/backups', function () { $backup = ScheduledDatabaseBackup::where('uuid', $response->json('uuid'))->firstOrFail(); - expect($backup->databases_to_backup)->toBe('app.db,jobs.db') + expect($backup->databases_to_backup)->toBeNull() ->and($backup->database_type)->toBe(StandaloneSqlite::class); }); + test('keeps explicitly selected sqlite backup files', function () { + $database = create_standalone_sqlite($this->environment->id, $this->destination, ['sqlite_databases' => 'app.db,jobs.db']); + + $response = $this->withHeaders(backupHeaders()) + ->postJson("/api/v1/databases/{$database->uuid}/backups", [ + 'frequency' => 'daily', + 'databases_to_backup' => 'jobs.db', + ]); + + $response->assertCreated(); + + expect(ScheduledDatabaseBackup::where('uuid', $response->json('uuid'))->firstOrFail()->databases_to_backup)->toBe('jobs.db'); + }); + test('creates backup configuration with valid frequency', function () { $response = $this->withHeaders(backupHeaders()) ->postJson("/api/v1/databases/{$this->database->uuid}/backups", [ diff --git a/tests/Feature/CreateScheduledBackupValidationTest.php b/tests/Feature/CreateScheduledBackupValidationTest.php index 23a212b3bc..18e6e33d0a 100644 --- a/tests/Feature/CreateScheduledBackupValidationTest.php +++ b/tests/Feature/CreateScheduledBackupValidationTest.php @@ -3,6 +3,8 @@ use App\Jobs\DatabaseBackupJob; use App\Livewire\Project\Database\CreateScheduledBackup; use App\Models\Environment; +use App\Models\InstanceSettings; +use App\Models\PrivateKey; use App\Models\Project; use App\Models\ScheduledDatabaseBackup; use App\Models\Server; @@ -16,6 +18,8 @@ use App\Models\StandaloneSqlite; use App\Models\Team; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\Notification; +use Illuminate\Support\Facades\Process; use Livewire\Livewire; uses(RefreshDatabase::class); @@ -201,7 +205,41 @@ it('creates a sqlite backup for its configured database files', function () { $backup = ScheduledDatabaseBackup::firstOrFail(); expect($backup->database_type)->toBe(StandaloneSqlite::class) - ->and($backup->databases_to_backup)->toBe('app.db,jobs.db'); + ->and($backup->databases_to_backup)->toBeNull(); +}); + +it('backs up sqlite files added to the database after the backup was scheduled', function () { + InstanceSettings::forceCreate(['id' => 0]); + Notification::fake(); + $this->server->update(['private_key_id' => PrivateKey::factory()->create(['team_id' => $this->team->id])->id]); + $this->server->settings()->update(['is_reachable' => true, 'is_usable' => true]); + $database = create_standalone_sqlite($this->environment->id, $this->destination, [ + 'sqlite_databases' => 'app.db', + 'status' => 'running:healthy', + ]); + + Livewire::test(CreateScheduledBackup::class, ['database' => $database]) + ->set('frequency', 'daily') + ->call('submit'); + + $database->update(['sqlite_databases' => 'app.db,audit.db']); + + $commands = collect(); + Process::fake(function ($process) use ($commands) { + $commands->push(is_array($process->command) ? implode(' ', $process->command) : $process->command); + + return Process::result(output: ''); + }); + + try { + (new DatabaseBackupJob(ScheduledDatabaseBackup::query()->sole()))->handle(); + } catch (Throwable) { + // Faked remote commands return no backup size; only the files the job dumped matter here. + } + + $dumpedFiles = $commands->filter(fn (string $command): bool => str_contains($command, 'VACUUM INTO'))->implode("\n"); + expect($dumpedFiles)->toContain('/var/lib/sqlite/app.db') + ->toContain('/var/lib/sqlite/audit.db'); }); it('rejects scheduled backups for unsupported database types', function () {