fix: allow archiving backup sets with restore runs
This commit is contained in:
parent
70fd5d2e68
commit
160c5e42a9
@ -78,16 +78,6 @@ public static function table(Table $table): Table
|
|||||||
->requiresConfirmation()
|
->requiresConfirmation()
|
||||||
->visible(fn (BackupSet $record) => ! $record->trashed())
|
->visible(fn (BackupSet $record) => ! $record->trashed())
|
||||||
->action(function (BackupSet $record, AuditLogger $auditLogger) {
|
->action(function (BackupSet $record, AuditLogger $auditLogger) {
|
||||||
if ($record->restoreRuns()->withTrashed()->exists()) {
|
|
||||||
Notification::make()
|
|
||||||
->title('Cannot archive backup set')
|
|
||||||
->body('Backup sets used by restore runs cannot be archived.')
|
|
||||||
->danger()
|
|
||||||
->send();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$record->delete();
|
$record->delete();
|
||||||
|
|
||||||
if ($record->tenant) {
|
if ($record->tenant) {
|
||||||
@ -159,7 +149,7 @@ public static function table(Table $table): Table
|
|||||||
|
|
||||||
return $isOnlyTrashed;
|
return $isOnlyTrashed;
|
||||||
})
|
})
|
||||||
->modalDescription('This archives backup sets (soft delete). Backup sets referenced by restore runs will be skipped.')
|
->modalDescription('This archives backup sets (soft delete). Already archived backup sets will be skipped.')
|
||||||
->form(function (Collection $records) {
|
->form(function (Collection $records) {
|
||||||
if ($records->count() >= 10) {
|
if ($records->count() >= 10) {
|
||||||
return [
|
return [
|
||||||
|
|||||||
@ -82,14 +82,6 @@ public function handle(BulkOperationService $service): void
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($backupSet->restoreRuns()->withTrashed()->exists()) {
|
|
||||||
$service->recordSkippedWithReason($run, (string) $backupSet->id, 'Referenced by restore runs');
|
|
||||||
$skipped++;
|
|
||||||
$skipReasons['Referenced by restore runs'] = ($skipReasons['Referenced by restore runs'] ?? 0) + 1;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$backupSet->delete();
|
$backupSet->delete();
|
||||||
$service->recordSuccess($run);
|
$service->recordSuccess($run);
|
||||||
$succeeded++;
|
$succeeded++;
|
||||||
|
|||||||
@ -32,7 +32,7 @@ public function tenant(): BelongsTo
|
|||||||
|
|
||||||
public function backupSet(): BelongsTo
|
public function backupSet(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(BackupSet::class);
|
return $this->belongsTo(BackupSet::class)->withTrashed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scopeDeletable($query)
|
public function scopeDeletable($query)
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
use App\Models\BackupItem;
|
use App\Models\BackupItem;
|
||||||
use App\Models\BackupSet;
|
use App\Models\BackupSet;
|
||||||
use App\Models\BulkOperationRun;
|
use App\Models\BulkOperationRun;
|
||||||
|
use App\Models\RestoreRun;
|
||||||
use App\Models\Tenant;
|
use App\Models\Tenant;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
@ -55,6 +56,35 @@
|
|||||||
expect($bulkRun->status)->toBe('completed');
|
expect($bulkRun->status)->toBe('completed');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('backup sets can be archived even when referenced by restore runs', function () {
|
||||||
|
$tenant = Tenant::factory()->create();
|
||||||
|
$tenant->makeCurrent();
|
||||||
|
$user = User::factory()->create();
|
||||||
|
|
||||||
|
$set = BackupSet::create([
|
||||||
|
'tenant_id' => $tenant->id,
|
||||||
|
'name' => 'Backup',
|
||||||
|
'status' => 'completed',
|
||||||
|
'item_count' => 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$restoreRun = RestoreRun::create([
|
||||||
|
'tenant_id' => $tenant->id,
|
||||||
|
'backup_set_id' => $set->id,
|
||||||
|
'status' => 'completed',
|
||||||
|
'is_dry_run' => true,
|
||||||
|
'requested_by' => 'tester@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Livewire::actingAs($user)
|
||||||
|
->test(BackupSetResource\Pages\ListBackupSets::class)
|
||||||
|
->callTableBulkAction('bulk_delete', collect([$set]))
|
||||||
|
->assertHasNoTableBulkActionErrors();
|
||||||
|
|
||||||
|
expect(BackupSet::withTrashed()->find($set->id)?->trashed())->toBeTrue();
|
||||||
|
expect(RestoreRun::withTrashed()->find($restoreRun->id))->not->toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
test('backup sets table bulk archive requires type-to-confirm for 10+ sets', function () {
|
test('backup sets table bulk archive requires type-to-confirm for 10+ sets', function () {
|
||||||
$tenant = Tenant::factory()->create();
|
$tenant = Tenant::factory()->create();
|
||||||
$tenant->makeCurrent();
|
$tenant->makeCurrent();
|
||||||
|
|||||||
@ -53,7 +53,7 @@
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('backup set archive is blocked when restore runs exist', function () {
|
test('backup set can be archived when restore runs exist', function () {
|
||||||
$tenant = Tenant::create([
|
$tenant = Tenant::create([
|
||||||
'tenant_id' => 'tenant-2',
|
'tenant_id' => 'tenant-2',
|
||||||
'name' => 'Tenant 2',
|
'name' => 'Tenant 2',
|
||||||
@ -65,7 +65,7 @@
|
|||||||
'status' => 'completed',
|
'status' => 'completed',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
RestoreRun::create([
|
$restoreRun = RestoreRun::create([
|
||||||
'tenant_id' => $tenant->id,
|
'tenant_id' => $tenant->id,
|
||||||
'backup_set_id' => $backupSet->id,
|
'backup_set_id' => $backupSet->id,
|
||||||
'status' => 'completed',
|
'status' => 'completed',
|
||||||
@ -77,12 +77,13 @@
|
|||||||
Livewire::test(ListBackupSets::class)
|
Livewire::test(ListBackupSets::class)
|
||||||
->callTableAction('archive', $backupSet);
|
->callTableAction('archive', $backupSet);
|
||||||
|
|
||||||
$this->assertDatabaseMissing('audit_logs', [
|
$this->assertSoftDeleted('backup_sets', ['id' => $backupSet->id]);
|
||||||
|
$this->assertDatabaseHas('audit_logs', [
|
||||||
'resource_type' => 'backup_set',
|
'resource_type' => 'backup_set',
|
||||||
'resource_id' => (string) $backupSet->id,
|
'resource_id' => (string) $backupSet->id,
|
||||||
'action' => 'backup.deleted',
|
'action' => 'backup.deleted',
|
||||||
]);
|
]);
|
||||||
$this->assertDatabaseHas('backup_sets', ['id' => $backupSet->id, 'deleted_at' => null]);
|
$this->assertDatabaseHas('restore_runs', ['id' => $restoreRun->id]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('backup set can be force deleted when trashed and unused', function () {
|
test('backup set can be force deleted when trashed and unused', function () {
|
||||||
|
|||||||
@ -55,7 +55,7 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('bulk backup set delete job skips sets referenced by restore runs', function () {
|
test('bulk backup set delete job archives sets even when referenced by restore runs', function () {
|
||||||
$tenant = Tenant::factory()->create();
|
$tenant = Tenant::factory()->create();
|
||||||
$user = User::factory()->create();
|
$user = User::factory()->create();
|
||||||
|
|
||||||
@ -82,10 +82,10 @@
|
|||||||
$run->refresh();
|
$run->refresh();
|
||||||
expect($run->status)->toBe('completed')
|
expect($run->status)->toBe('completed')
|
||||||
->and($run->processed_items)->toBe(1)
|
->and($run->processed_items)->toBe(1)
|
||||||
->and($run->succeeded)->toBe(0)
|
->and($run->succeeded)->toBe(1)
|
||||||
->and($run->failed)->toBe(0)
|
->and($run->failed)->toBe(0)
|
||||||
->and($run->skipped)->toBe(1);
|
->and($run->skipped)->toBe(0);
|
||||||
|
|
||||||
expect(collect($run->failures)->pluck('reason')->join(' '))->toContain('restore runs');
|
expect(BackupSet::withTrashed()->find($set->id)?->trashed())->toBeTrue();
|
||||||
expect(BackupSet::withTrashed()->find($set->id)?->trashed())->toBeFalse();
|
expect(RestoreRun::query()->where('backup_set_id', $set->id)->exists())->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user