expired()->create(); $active = Share::factory()->expiresInHours(24)->create(); $noExpiry = Share::factory()->create(['expires_at' => null]); $this->artisan('shares:cleanup') ->expectsOutputToContain('Cleaned up 1 expired share(s)') ->assertExitCode(0); expect(Share::query()->find($expired->id))->toBeNull(); expect(Share::query()->find($active->id))->not->toBeNull(); expect(Share::query()->find($noExpiry->id))->not->toBeNull(); }); test('cleanup removes shares that reached download limit', function () { Storage::fake('shares'); $reachedLimit = Share::factory()->withMaxDownloads(5)->create(['download_count' => 5]); $underLimit = Share::factory()->withMaxDownloads(5)->create(['download_count' => 3]); $this->artisan('shares:cleanup') ->expectsOutputToContain('Cleaned up 1 expired share(s)') ->assertExitCode(0); expect(Share::query()->find($reachedLimit->id))->toBeNull(); expect(Share::query()->find($underLimit->id))->not->toBeNull(); }); test('cleanup handles no expired shares', function () { Storage::fake('shares'); Share::factory()->create(['expires_at' => null]); $this->artisan('shares:cleanup') ->expectsOutputToContain('Cleaned up 0 expired share(s)') ->assertExitCode(0); }); test('cleanup removes both expired and download-limited shares', function () { Storage::fake('shares'); $expired = Share::factory()->expired()->create(); $limitReached = Share::factory()->withMaxDownloads(1)->create(['download_count' => 1]); $active = Share::factory()->create(['expires_at' => null]); $this->artisan('shares:cleanup') ->expectsOutputToContain('Cleaned up 2 expired share(s)') ->assertExitCode(0); expect(Share::query()->find($expired->id))->toBeNull(); expect(Share::query()->find($limitReached->id))->toBeNull(); expect(Share::query()->find($active->id))->not->toBeNull(); }); test('cleanup removes uploads no chunk reached for 4 hours, and keeps recent ones and completed shares', function () { Storage::fake('shares'); $this->freezeTime(); $abandoned = Share::factory()->pending()->create(['updated_at' => now()->subHours(4)->subMinute()]); Storage::disk('shares')->put($abandoned->token.'/file.enc', 'encrypted'); $recent = Share::factory()->pending()->create(['updated_at' => now()->subHours(3)]); $completed = Share::factory()->create(['updated_at' => now()->subDays(3)]); $this->artisan('shares:cleanup') ->expectsOutputToContain('Cleaned up 1 abandoned upload(s)') ->assertExitCode(0); $this->assertModelMissing($abandoned); $this->assertModelExists($recent); $this->assertModelExists($completed); expect(Storage::disk('shares')->directories())->not->toContain($abandoned->token); }); test('cleanup removes temporary upload files older than 4 hours and keeps newer ones', function () { $storage = FileUploadConfiguration::storage(); $storage->put(FileUploadConfiguration::path('old.pdf'), 'unencrypted leftover'); $storage->put(FileUploadConfiguration::path('new.png'), 'a logo being chosen'); touch($storage->path(FileUploadConfiguration::path('old.pdf')), now()->subHours(5)->getTimestamp()); $this->artisan('shares:cleanup') ->expectsOutputToContain('Cleaned up 1 temporary upload file(s)') ->assertExitCode(0); expect($storage->exists(FileUploadConfiguration::path('old.pdf')))->toBeFalse(); expect($storage->exists(FileUploadConfiguration::path('new.png')))->toBeTrue(); });