plantedBackups as $filename) { Storage::delete('app/backups/'.$filename); } parent::tearDown(); } public function test_invalid_zip_archive_aborts_before_wiping_database(): void { Artisan::spy(); $filename = 'corrupt-'.uniqid().'.zip'; Storage::put('app/backups/'.$filename, 'this is not actually a zip file'); $this->plantedBackups[] = $filename; $superuser = User::factory()->superuser()->create(); $this->actingAs($superuser) ->post(route('settings.backups.restore', $filename)) ->assertRedirect(route('settings.backups.index')) ->assertSessionHas('error'); Artisan::shouldNotHaveReceived('call', function ($command) { return in_array($command, ['db:wipe', 'snipeit:restore', 'migrate'], true); }); } public function test_missing_zip_extension_aborts_before_wiping_database(): void { // ZipArchive is loaded in test environments, so we cannot literally // remove ext-zip mid-run. This test documents the intent: if the // extension is missing, postRestore should NOT call db:wipe. The // pre-fix flow called db:wipe unconditionally regardless of what // downstream commands could do, which is the exact hazard. // // The class_exists gate on ZipArchive::class is the single line // that enforces this. Marking as incomplete so that if a future // refactor removes the gate the intent is still discoverable. $this->markTestIncomplete('ZipArchive is loaded in the PHP test image; guard is source-verified in SettingsController::postRestore.'); } }