remcacheDir = storage_path('app/remcache'); if (! is_dir($this->remcacheDir)) { mkdir($this->remcacheDir, 0755, true); } }); afterEach(function () { // Clean up any temp files this test created in the real remcache dir. foreach (glob($this->remcacheDir.'/{,.}pf-maint-test-*', GLOB_BRACE) ?: [] as $f) { @unlink($f); } }); it('deletes stale remcache files older than the cutoff and preserves fresh ones and dotfiles', function () { $stale = $this->remcacheDir.'/pf-maint-test-stale.tmp'; $fresh = $this->remcacheDir.'/pf-maint-test-fresh.tmp'; // A stale dotfile (like the directory's .gitignore) must be preserved. $dot = $this->remcacheDir.'/.pf-maint-test-keep'; file_put_contents($stale, 'old'); file_put_contents($fresh, 'new'); file_put_contents($dot, 'keep'); touch($stale, now()->subHours(48)->getTimestamp()); touch($dot, now()->subHours(48)->getTimestamp()); $this->artisan('storage:maintenance', ['--only' => 'remcache', '--hours' => 24]) ->assertExitCode(0); expect(is_file($stale))->toBeFalse(); expect(is_file($fresh))->toBeTrue(); // Dotfiles (e.g. .gitignore) are always preserved. expect(is_file($dot))->toBeTrue(); }); it('dry-run reports remcache deletions without removing files', function () { $stale = $this->remcacheDir.'/pf-maint-test-stale.tmp'; file_put_contents($stale, 'old'); touch($stale, now()->subHours(48)->getTimestamp()); $this->artisan('storage:maintenance', ['--only' => 'remcache', '--hours' => 24, '--dry-run' => true]) ->assertExitCode(0); expect(is_file($stale))->toBeTrue(); }); it('prunes empty directories across managed trees while keeping live files', function () { $disk = Storage::disk('local'); // Empty leftovers. $disk->makeDirectory('public/m/_v2/9/month/rand'); $disk->makeDirectory('public/_esm.t3/m/u/leaf'); $disk->makeDirectory('public/avatars/000/111/222'); $disk->makeDirectory('imports/42'); $disk->makeDirectory('story_archives/7/202601'); // Live files that must survive. $disk->put('public/m/_v2/9/month/keep/live.jpg', 'x'); $disk->put('imports/99/live.jpg', 'y'); $this->artisan('storage:maintenance', ['--only' => 'empty-dirs']) ->assertExitCode(0); expect($disk->directoryExists('public/m/_v2/9/month/rand'))->toBeFalse(); expect($disk->directoryExists('public/_esm.t3/m'))->toBeFalse(); expect($disk->directoryExists('public/avatars/000'))->toBeFalse(); expect($disk->directoryExists('imports/42'))->toBeFalse(); expect($disk->directoryExists('story_archives/7'))->toBeFalse(); // Roots and live branches preserved. expect($disk->exists('public/m/_v2/9/month/keep/live.jpg'))->toBeTrue(); expect($disk->exists('imports/99/live.jpg'))->toBeTrue(); }); it('dry-run reports empty-dir removals without deleting them', function () { $disk = Storage::disk('local'); $disk->makeDirectory('public/m/_v2/9/month/rand'); $this->artisan('storage:maintenance', ['--only' => 'empty-dirs', '--dry-run' => true]) ->assertExitCode(0); expect($disk->directoryExists('public/m/_v2/9/month/rand'))->toBeTrue(); }); it('rejects an --hours value below 1', function () { $this->artisan('storage:maintenance', ['--only' => 'remcache', '--hours' => 0]) ->assertExitCode(1); }); it('errors when --only matches no valid task', function () { $this->artisan('storage:maintenance', ['--only' => 'bogus']) ->assertExitCode(1); });