get(route('upload')); $response->assertOk(); }); test('upload page requires system password when configured', function () { Setting::set('system_password', bcrypt('system-secret')); $response = $this->get(route('upload')); $response->assertRedirect(route('system-password')); }); test('upload page accessible after system password verified', function () { Setting::set('system_password', bcrypt('system-secret')); $response = $this->withSession(['system_password_verified' => true]) ->get(route('upload')); $response->assertOk(); }); test('file upload creates share', function () { Storage::fake('shares'); $file = UploadedFile::fake()->create('document.pdf', 1024); Livewire::test(FileUploader::class) ->set('files', [$file]) ->call('createShare') ->assertRedirectContains('/share/'); expect(Share::query()->count())->toBe(1); $share = Share::query()->first(); expect($share->files)->toHaveCount(1); expect($share->files->first()->original_name)->toBe('document.pdf'); }); test('file upload with password creates password-protected share', function () { Storage::fake('shares'); $file = UploadedFile::fake()->create('secret.txt', 512); Livewire::test(FileUploader::class) ->set('files', [$file]) ->set('usePassword', true) ->set('password', 'my-password') ->call('createShare') ->assertRedirectContains('/share/'); $share = Share::query()->first(); expect($share->isPasswordProtected())->toBeTrue(); }); test('switching password protection on leaves the field empty and offers a Generate button by default', function () { $component = Livewire::test(FileUploader::class) ->set('usePassword', true); $component->assertSet('password', '') ->assertSeeHtml('data-test="generate-password"') ->assertSeeHtml('data-test="copy-password"'); }); test('a generated password protects the share and is flashed, encrypted, for the page the upload leads to', function () { Storage::fake('shares'); $component = Livewire::test(FileUploader::class) ->set('files', [UploadedFile::fake()->create('secret.txt', 512)]) ->set('usePassword', true) ->call('generatePassword'); $password = $component->get('password'); $component->call('createShare'); $share = Share::query()->first(); expect($password)->toMatch('/^[A-Za-z0-9]{20}$/'); expect(app(ShareService::class)->verifyPassword($share, $password))->toBeTrue(); expect(session('share_password.token'))->toBe($share->token); expect(Crypt::decryptString(session('share_password.password')))->toBe($password); }); test('a share without a password flashes no password', function () { Storage::fake('shares'); Livewire::test(FileUploader::class) ->set('files', [UploadedFile::fake()->create('document.pdf', 100)]) ->call('createShare') ->assertRedirectContains('/share/'); expect(session()->has('share_password'))->toBeFalse(); }); test('a prefilling generator fills in a password as protection is switched on', function () { Setting::set('password_generator_mode', 'prefill'); $component = Livewire::test(FileUploader::class) ->set('usePassword', true); expect($component->get('password'))->toMatch('/^[A-Za-z0-9]{20}$/'); }); test('a prefilling generator keeps a password the uploader already typed', function () { Setting::set('password_generator_mode', 'prefill'); Livewire::test(FileUploader::class) ->set('password', 'my-own-password') ->set('usePassword', true) ->assertSet('password', 'my-own-password'); }); test('a generator switched off offers no Generate button and generates nothing', function () { Setting::set('password_generator_mode', 'off'); Livewire::test(FileUploader::class) ->set('usePassword', true) ->assertDontSeeHtml('data-test="generate-password"') ->assertSeeHtml('data-test="copy-password"') ->call('generatePassword') ->assertSet('password', ''); }); test('file upload with expiration sets expires_at', function () { Storage::fake('shares'); $file = UploadedFile::fake()->create('file.txt', 256); Livewire::test(FileUploader::class) ->set('files', [$file]) ->set('expiration', '24h') ->call('createShare') ->assertRedirectContains('/share/'); $share = Share::query()->first(); expect($share->expires_at)->not->toBeNull(); }); test('file upload with max downloads sets limit', function () { Storage::fake('shares'); $file = UploadedFile::fake()->create('file.txt', 256); Livewire::test(FileUploader::class) ->set('files', [$file]) ->set('maxDownloads', 5) ->call('createShare') ->assertRedirectContains('/share/'); $share = Share::query()->first(); expect($share->max_downloads)->toBe(5); }); test('every upload batch dispatches files-processed to clear the uploading state', function () { Storage::fake('shares'); Livewire::test(FileUploader::class) ->set('files', [UploadedFile::fake()->create('first.txt', 64)]) ->assertDispatched('files-processed') ->set('files', [UploadedFile::fake()->create('second.txt', 64)]) ->assertDispatched('files-processed'); }); test('files added in multiple batches end up in the same share', function () { Storage::fake('shares'); Livewire::test(FileUploader::class) ->set('files', [UploadedFile::fake()->create('first.txt', 64)]) ->set('files', [UploadedFile::fake()->create('second.txt', 64)]) ->call('createShare') ->assertHasNoErrors() ->assertRedirectContains('/share/'); $share = Share::query()->first(); expect($share->files->pluck('original_name')->all())->toBe(['first.txt', 'second.txt']); }); test('files larger than 4 GB can be shared when within the admin file size limit', function () { Storage::fake('shares'); Setting::set('max_file_size', 15000 * 1024 * 1024); Setting::set('max_size_per_share', 20 * 1024 * 1024 * 1024); Livewire::test(FileUploader::class) ->set('files', [UploadedFile::fake()->create('backup.dump', 6 * 1024 * 1024)]) ->assertHasNoErrors('files') ->call('createShare') ->assertHasNoErrors() ->assertRedirectContains('/share/'); expect(Share::query()->first()->total_size)->toBe(6 * 1024 * 1024 * 1024); }); test('a rejected upload logs the real reason instead of blaming the file size limit', function () { Log::spy(); Setting::set('max_file_size', 15000 * 1024 * 1024); $errors = ['files.0' => ['The files.0 failed to upload.']]; $component = Livewire::test(FileUploader::class) ->call('_uploadErrored', 'files', json_encode(['errors' => $errors]), true) ->assertDispatched('upload:errored'); expect($component->errors()->first('files')) ->toBe('Upload failed: the server could not accept the file. Please try again or contact the administrator.'); Log::shouldHaveReceived('warning') ->withArgs(fn (string $message, array $context): bool => $context['errors'] === $errors) ->once(); }); test('file upload requires at least one file', function () { Livewire::test(FileUploader::class) ->set('files', []) ->call('createShare') ->assertHasErrors(['files']); }); test('file upload blocks when storage is full', function () { Storage::fake('shares'); Setting::set('max_storage_quota', 100); Share::factory()->create(['total_size' => 100]); $file = UploadedFile::fake()->create('file.txt', 1); Livewire::test(FileUploader::class) ->set('files', [$file]) ->call('createShare') ->assertHasErrors(['files']); }); test('system password prompt verifies correct password', function () { Setting::set('system_password', bcrypt('system-secret')); Livewire::test(SystemPasswordPrompt::class) ->set('password', 'system-secret') ->call('verify') ->assertRedirect(route('upload')); }); test('system password prompt rejects incorrect password', function () { Setting::set('system_password', bcrypt('system-secret')); Livewire::test(SystemPasswordPrompt::class) ->set('password', 'wrong') ->call('verify') ->assertHasErrors(['password']); });