get(route('share.download', $share)); $response->assertOk(); }); test('share download page returns 404 for expired share', function () { $share = Share::factory()->expired()->create(); $response = $this->get(route('share.download', $share)); $response->assertNotFound(); }); test('share download page returns 404 when download limit reached', function () { $share = Share::factory()->withMaxDownloads(1)->create(['download_count' => 1]); $response = $this->get(route('share.download', $share)); $response->assertNotFound(); }); test('share download page shows password form for password-protected share', function () { Storage::fake('shares'); $share = createShareWithFile('secret-pass'); $response = $this->get(route('share.download', $share)); $response->assertOk(); $response->assertSee('password'); }); test('password verification works for protected share', function () { Storage::fake('shares'); $share = createShareWithFile('my-password'); Livewire::test(ShareDownload::class, ['share' => $share]) ->assertSet('authenticated', false) ->set('password', 'my-password') ->call('verifyPassword') ->assertSet('authenticated', true) ->assertHasNoErrors(); }); test('wrong password is rejected', function () { Storage::fake('shares'); $share = createShareWithFile('my-password'); Livewire::test(ShareDownload::class, ['share' => $share]) ->set('password', 'wrong-password') ->call('verifyPassword') ->assertSet('authenticated', false) ->assertHasErrors(['password']); }); test('non-password share shows files directly', function () { Storage::fake('shares'); $share = createShareWithFile(); Livewire::test(ShareDownload::class, ['share' => $share]) ->assertSet('authenticated', true); }); test('download counter increments on zip download', function () { Storage::fake('shares'); $share = createShareWithFile(); $response = $this->get(route('share.download.all', $share)); $response->streamedContent(); $response->assertDownload('share-'.$share->token.'.zip'); expect($share->fresh()->download_count)->toBe(1); }); test('zip download streams a valid archive with every file\'s original content', function () { Storage::fake('shares'); config(['uploads.chunk_size' => 1000]); $binary = random_bytes(2500); $share = app(ShareService::class)->createShare([ ['file' => UploadedFile::fake()->createWithContent('notes.txt', 'hello zip content'), 'relativePath' => null], ['file' => UploadedFile::fake()->createWithContent('photo.bin', $binary), 'relativePath' => 'holiday/photo.bin'], ]); $zipPath = tempnam(sys_get_temp_dir(), 'zip'); file_put_contents($zipPath, $this->get(route('share.download.all', $share))->streamedContent()); $zip = new ZipArchive; expect($zip->open($zipPath))->toBeTrue(); expect($zip->numFiles)->toBe(2); expect($zip->getFromName('notes.txt'))->toBe('hello zip content'); expect($zip->getFromName('holiday/photo.bin'))->toBe($binary); $zip->close(); unlink($zipPath); }); test('last download streams successfully before auto-delete', function () { Storage::fake('shares'); $share = createShareWithFile(); $share->update(['max_downloads' => 1]); $content = $this->get(route('share.download.all', $share))->streamedContent(); expect($content)->toStartWith("PK\x03\x04"); $this->assertModelMissing($share); }); test('a share whose files are still uploading is not found anywhere a recipient or uploader could open it', function (string $route) { Storage::fake('shares'); $share = Share::factory()->pending()->create(); $file = ShareFile::factory()->for($share)->uploading()->create(); $response = $this->get(route($route, ['share' => $share, 'shareFile' => $file])); $response->assertNotFound(); })->with([ 'download page' => 'share.download', 'download all' => 'share.download.all', 'download one file' => 'share.download.file', 'share created page' => 'share.created', ]); test('a password share created before key wrapping still unlocks and downloads', function () { Storage::fake('shares'); $salt = str_repeat('cd', 32); $share = Share::factory()->withPassword('old-password')->create(['encryption_salt' => $salt]); $file = ShareFile::factory()->for($share)->create(['stored_path' => 'shares/'.$share->token.'/old.enc', 'file_size' => 11]); $source = tempnam(sys_get_temp_dir(), 'old'); file_put_contents($source, 'old content'); Storage::disk('shares')->makeDirectory($share->token); app(FileEncryptionService::class)->encryptFile($source, Storage::disk('shares')->path($share->token.'/old.enc'), bin2hex(hash_pbkdf2('sha256', 'old-password', hex2bin($salt), 100000, 32, true)), 1024); unlink($source); Livewire::test(ShareDownload::class, ['share' => $share]) ->set('password', 'old-password') ->call('verifyPassword') ->assertSet('authenticated', true); expect($this->get(route('share.download.file', [$share, $file]))->streamedContent())->toBe('old content'); }); test('share auto-deletes after reaching download limit', function () { Storage::fake('shares'); $service = app(ShareService::class); $file = UploadedFile::fake()->create('file.txt', 100); $share = $service->createShare([ ['file' => $file, 'relativePath' => null], ], [ 'max_downloads' => 1, ]); $service->recordDownload($share); expect(Share::query()->find($share->id))->toBeNull(); }); /** * Helper to create a share with an actual encrypted file. */ function createShareWithFile(?string $password = null, string $content = 'test content'): Share { return app(ShareService::class)->createShare([ ['file' => UploadedFile::fake()->createWithContent('testfile.txt', $content), 'relativePath' => null], ], [ 'password' => $password, ]); }