Livewire's temporary upload rule was hard-coded to max:4194304 (4 GB), so /livewire/upload-file rejected any larger file no matter how high PHP_UPLOAD_MAX_FILESIZE or the admin's max file size were set. _uploadErrored() then replaced Livewire's actual message with "file exceeds the maximum size of N MB", quoting the admin limit the file was under. The cap is removed: PHP's upload_max_filesize is the hard limit and the admin setting is still enforced in updatedFiles() and createShare(). A rejected upload now logs the real validation errors and tells the user the server could not accept the file. max_upload_time is configurable via LIVEWIRE_MAX_UPLOAD_TIME, and the README documents every limit large uploads depend on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017XYnWFt9pJEwvAmNFN38XD
193 lines
6.1 KiB
PHP
193 lines
6.1 KiB
PHP
<?php
|
|
|
|
use App\Livewire\FileUploader;
|
|
use App\Livewire\SystemPasswordPrompt;
|
|
use App\Models\Setting;
|
|
use App\Models\Share;
|
|
use Illuminate\Http\UploadedFile;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Livewire\Livewire;
|
|
|
|
test('upload page can be rendered', function () {
|
|
$response = $this->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('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']);
|
|
});
|