diff --git a/app/Concerns/PasswordValidationRules.php b/app/Concerns/PasswordValidationRules.php index 9b45ef0..e858302 100644 --- a/app/Concerns/PasswordValidationRules.php +++ b/app/Concerns/PasswordValidationRules.php @@ -2,6 +2,7 @@ namespace App\Concerns; +use Illuminate\Contracts\Validation\Rule; use Illuminate\Validation\Rules\Password; trait PasswordValidationRules @@ -9,7 +10,7 @@ trait PasswordValidationRules /** * Get the validation rules used to validate passwords. * - * @return array|string> + * @return array|string> */ protected function passwordRules(): array { @@ -19,7 +20,7 @@ trait PasswordValidationRules /** * Get the validation rules used to validate the current password. * - * @return array|string> + * @return array|string> */ protected function currentPasswordRules(): array { diff --git a/app/Http/Controllers/DownloadController.php b/app/Http/Controllers/DownloadController.php index 45fefd8..6449cfd 100644 --- a/app/Http/Controllers/DownloadController.php +++ b/app/Http/Controllers/DownloadController.php @@ -8,6 +8,7 @@ use App\Services\FileEncryptionService; use App\Services\ShareService; use Illuminate\Support\Facades\Storage; use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\HeaderUtils; use Symfony\Component\HttpFoundation\StreamedResponse; use ZipArchive; @@ -71,7 +72,7 @@ class DownloadController extends Controller $headers = [ 'Content-Type' => $mimeType, - 'Content-Disposition' => \Symfony\Component\HttpFoundation\HeaderUtils::makeDisposition( + 'Content-Disposition' => HeaderUtils::makeDisposition( 'attachment', $shareFile->original_name, 'download', diff --git a/app/Http/Middleware/SecurityHeaders.php b/app/Http/Middleware/SecurityHeaders.php index 571fd5f..76cca8c 100644 --- a/app/Http/Middleware/SecurityHeaders.php +++ b/app/Http/Middleware/SecurityHeaders.php @@ -11,7 +11,7 @@ class SecurityHeaders /** * Handle an incoming request. * - * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next + * @param Closure(Request): (Response) $next */ public function handle(Request $request, Closure $next): Response { diff --git a/app/Livewire/FileUploader.php b/app/Livewire/FileUploader.php index 39a5d4b..c0c259b 100644 --- a/app/Livewire/FileUploader.php +++ b/app/Livewire/FileUploader.php @@ -7,6 +7,7 @@ use App\Services\ShareService; use Illuminate\Validation\ValidationException; use Livewire\Attributes\Layout; use Livewire\Component; +use Livewire\Features\SupportFileUploads\TemporaryUploadedFile; use Livewire\WithFileUploads; #[Layout('layouts.app')] @@ -14,7 +15,7 @@ class FileUploader extends Component { use WithFileUploads; - /** @var array */ + /** @var array */ public array $files = []; /** @var array */ diff --git a/app/Models/User.php b/app/Models/User.php index 832c364..0cce23f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,6 +3,7 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; +use Database\Factories\UserFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; @@ -11,7 +12,7 @@ use Laravel\Fortify\TwoFactorAuthenticatable; class User extends Authenticatable { - /** @use HasFactory<\Database\Factories\UserFactory> */ + /** @use HasFactory */ use HasFactory, Notifiable, TwoFactorAuthenticatable; /** diff --git a/app/Services/ShareService.php b/app/Services/ShareService.php index 4492920..5fecf4b 100644 --- a/app/Services/ShareService.php +++ b/app/Services/ShareService.php @@ -114,7 +114,7 @@ class ShareService { if ($share->isPasswordProtected()) { if (! $password) { - throw new \RuntimeException('Password required for this share'); + throw new RuntimeException('Password required for this share'); } return bin2hex($this->encryptionService->deriveKey($password, $share->encryption_salt)); diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 0ad9c57..5ffd769 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -1,6 +1,9 @@ [ 'users' => [ 'driver' => 'eloquent', - 'model' => env('AUTH_MODEL', App\Models\User::class), + 'model' => env('AUTH_MODEL', User::class), ], // 'users' => [ diff --git a/config/database.php b/config/database.php index df933e7..64709ce 100644 --- a/config/database.php +++ b/config/database.php @@ -1,6 +1,7 @@ true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ - (PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), + (PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], @@ -79,7 +80,7 @@ return [ 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ - (PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), + (PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 15e0717..1f62ce4 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -2,12 +2,13 @@ namespace Database\Factories; +use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; /** - * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User> + * @extends Factory */ class UserFactory extends Factory { diff --git a/tests/Feature/Admin/AdminDashboardTest.php b/tests/Feature/Admin/AdminDashboardTest.php index 095f0e0..e0585d5 100644 --- a/tests/Feature/Admin/AdminDashboardTest.php +++ b/tests/Feature/Admin/AdminDashboardTest.php @@ -1,5 +1,6 @@ id; Livewire::actingAs($admin) - ->test(\App\Livewire\Admin\AdminDashboard::class) + ->test(AdminDashboard::class) ->call('deleteShare', $shareId); expect(Share::query()->find($shareId))->toBeNull(); diff --git a/tests/Feature/Admin/AdminSettingsTest.php b/tests/Feature/Admin/AdminSettingsTest.php index 962762b..9d644fc 100644 --- a/tests/Feature/Admin/AdminSettingsTest.php +++ b/tests/Feature/Admin/AdminSettingsTest.php @@ -1,7 +1,9 @@ where('is_admin', true)->first(); - $phpMaxMb = \App\Livewire\Admin\AdminSettings::phpMaxUploadMb(); + $phpMaxMb = AdminSettings::phpMaxUploadMb(); Livewire::actingAs($admin) - ->test(\App\Livewire\Admin\AdminSettings::class) + ->test(AdminSettings::class) ->set('maxFileSize', min(200, $phpMaxMb)) ->set('maxStorageQuota', 50) ->set('maxFilesPerShare', 100) @@ -50,10 +52,10 @@ test('admin can save settings', function () { test('admin can set system password', function () { $admin = User::query()->where('is_admin', true)->first(); - $phpMaxMb = \App\Livewire\Admin\AdminSettings::phpMaxUploadMb(); + $phpMaxMb = AdminSettings::phpMaxUploadMb(); Livewire::actingAs($admin) - ->test(\App\Livewire\Admin\AdminSettings::class) + ->test(AdminSettings::class) ->set('maxFileSize', $phpMaxMb) ->set('systemPassword', 'new-system-password') ->call('saveSettings') @@ -61,7 +63,7 @@ test('admin can set system password', function () { $storedPassword = Setting::get('system_password'); expect($storedPassword)->not->toBeNull(); - expect(\Illuminate\Support\Facades\Hash::check('new-system-password', $storedPassword))->toBeTrue(); + expect(Hash::check('new-system-password', $storedPassword))->toBeTrue(); }); test('admin can clear system password', function () { @@ -70,7 +72,7 @@ test('admin can clear system password', function () { Setting::set('system_password', bcrypt('existing-password')); Livewire::actingAs($admin) - ->test(\App\Livewire\Admin\AdminSettings::class) + ->test(AdminSettings::class) ->call('clearSystemPassword') ->assertHasNoErrors(); @@ -79,14 +81,14 @@ test('admin can clear system password', function () { test('settings page loads existing values', function () { $admin = User::query()->where('is_admin', true)->first(); - $phpMaxMb = \App\Livewire\Admin\AdminSettings::phpMaxUploadMb(); + $phpMaxMb = AdminSettings::phpMaxUploadMb(); $testSize = min(40, $phpMaxMb); Setting::set('max_file_size', $testSize * 1024 * 1024); Setting::set('max_files_per_share', 75); Livewire::actingAs($admin) - ->test(\App\Livewire\Admin\AdminSettings::class) + ->test(AdminSettings::class) ->assertSet('maxFileSize', $testSize) ->assertSet('maxFilesPerShare', 75); }); @@ -95,7 +97,7 @@ test('settings validation rejects invalid values', function () { $admin = User::query()->where('is_admin', true)->first(); Livewire::actingAs($admin) - ->test(\App\Livewire\Admin\AdminSettings::class) + ->test(AdminSettings::class) ->set('maxFileSize', 0) ->set('maxStorageQuota', 0) ->call('saveSettings') diff --git a/tests/Feature/Auth/AuthenticationTest.php b/tests/Feature/Auth/AuthenticationTest.php index efea44b..868b32f 100644 --- a/tests/Feature/Auth/AuthenticationTest.php +++ b/tests/Feature/Auth/AuthenticationTest.php @@ -66,4 +66,4 @@ test('users can logout', function () { $response->assertRedirect(route('home')); $this->assertGuest(); -}); \ No newline at end of file +}); diff --git a/tests/Feature/Auth/EmailVerificationTest.php b/tests/Feature/Auth/EmailVerificationTest.php index d893a7a..ef5ce75 100644 --- a/tests/Feature/Auth/EmailVerificationTest.php +++ b/tests/Feature/Auth/EmailVerificationTest.php @@ -64,4 +64,4 @@ test('already verified user visiting verification link is redirected without fir expect($user->fresh()->hasVerifiedEmail())->toBeTrue(); Event::assertNotDispatched(Verified::class); -}); \ No newline at end of file +}); diff --git a/tests/Feature/Auth/PasswordConfirmationTest.php b/tests/Feature/Auth/PasswordConfirmationTest.php index 514115a..76b5e34 100644 --- a/tests/Feature/Auth/PasswordConfirmationTest.php +++ b/tests/Feature/Auth/PasswordConfirmationTest.php @@ -8,4 +8,4 @@ test('confirm password screen can be rendered', function () { $response = $this->actingAs($user)->get(route('password.confirm')); $response->assertOk(); -}); \ No newline at end of file +}); diff --git a/tests/Feature/Auth/PasswordResetTest.php b/tests/Feature/Auth/PasswordResetTest.php index 4f451ec..47782b8 100644 --- a/tests/Feature/Auth/PasswordResetTest.php +++ b/tests/Feature/Auth/PasswordResetTest.php @@ -57,4 +57,4 @@ test('password can be reset with valid token', function () { return true; }); -}); \ No newline at end of file +}); diff --git a/tests/Feature/Auth/TwoFactorChallengeTest.php b/tests/Feature/Auth/TwoFactorChallengeTest.php index dc56ca8..4c117d1 100644 --- a/tests/Feature/Auth/TwoFactorChallengeTest.php +++ b/tests/Feature/Auth/TwoFactorChallengeTest.php @@ -29,4 +29,4 @@ test('two factor challenge can be rendered', function () { 'email' => $user->email, 'password' => 'password', ])->assertRedirect(route('two-factor.login')); -}); \ No newline at end of file +}); diff --git a/tests/Feature/DashboardTest.php b/tests/Feature/DashboardTest.php index d59d2d7..95c6a8b 100644 --- a/tests/Feature/DashboardTest.php +++ b/tests/Feature/DashboardTest.php @@ -13,4 +13,4 @@ test('authenticated users can visit the dashboard', function () { $response = $this->get(route('dashboard')); $response->assertOk(); -}); \ No newline at end of file +}); diff --git a/tests/Feature/FileUploadTest.php b/tests/Feature/FileUploadTest.php index 3e9eec8..600bd6a 100644 --- a/tests/Feature/FileUploadTest.php +++ b/tests/Feature/FileUploadTest.php @@ -1,5 +1,7 @@ create('document.pdf', 1024); - Livewire::test(\App\Livewire\FileUploader::class) + Livewire::test(FileUploader::class) ->set('files', [$file]) ->call('createShare') ->assertRedirectContains('/share/'); @@ -51,7 +53,7 @@ test('file upload with password creates password-protected share', function () { $file = UploadedFile::fake()->create('secret.txt', 512); - Livewire::test(\App\Livewire\FileUploader::class) + Livewire::test(FileUploader::class) ->set('files', [$file]) ->set('usePassword', true) ->set('password', 'my-password') @@ -67,7 +69,7 @@ test('file upload with expiration sets expires_at', function () { $file = UploadedFile::fake()->create('file.txt', 256); - Livewire::test(\App\Livewire\FileUploader::class) + Livewire::test(FileUploader::class) ->set('files', [$file]) ->set('expiration', '24h') ->call('createShare') @@ -82,7 +84,7 @@ test('file upload with max downloads sets limit', function () { $file = UploadedFile::fake()->create('file.txt', 256); - Livewire::test(\App\Livewire\FileUploader::class) + Livewire::test(FileUploader::class) ->set('files', [$file]) ->set('maxDownloads', 5) ->call('createShare') @@ -93,7 +95,7 @@ test('file upload with max downloads sets limit', function () { }); test('file upload requires at least one file', function () { - Livewire::test(\App\Livewire\FileUploader::class) + Livewire::test(FileUploader::class) ->set('files', []) ->call('createShare') ->assertHasErrors(['files']); @@ -106,7 +108,7 @@ test('file upload blocks when storage is full', function () { $file = UploadedFile::fake()->create('file.txt', 1); - Livewire::test(\App\Livewire\FileUploader::class) + Livewire::test(FileUploader::class) ->set('files', [$file]) ->call('createShare') ->assertHasErrors(['files']); @@ -115,7 +117,7 @@ test('file upload blocks when storage is full', function () { test('system password prompt verifies correct password', function () { Setting::set('system_password', bcrypt('system-secret')); - Livewire::test(\App\Livewire\SystemPasswordPrompt::class) + Livewire::test(SystemPasswordPrompt::class) ->set('password', 'system-secret') ->call('verify') ->assertRedirect(route('upload')); @@ -124,7 +126,7 @@ test('system password prompt verifies correct password', function () { test('system password prompt rejects incorrect password', function () { Setting::set('system_password', bcrypt('system-secret')); - Livewire::test(\App\Livewire\SystemPasswordPrompt::class) + Livewire::test(SystemPasswordPrompt::class) ->set('password', 'wrong') ->call('verify') ->assertHasErrors(['password']); diff --git a/tests/Feature/SecurityAuditTest.php b/tests/Feature/SecurityAuditTest.php index 74306a4..3e1a8c0 100644 --- a/tests/Feature/SecurityAuditTest.php +++ b/tests/Feature/SecurityAuditTest.php @@ -1,5 +1,6 @@ where('is_admin', true)->first(); - $phpMaxMb = \App\Livewire\Admin\AdminSettings::phpMaxUploadMb(); + $phpMaxMb = AdminSettings::phpMaxUploadMb(); Livewire::actingAs($admin) - ->test(\App\Livewire\Admin\AdminSettings::class) + ->test(AdminSettings::class) ->set('maxFileSize', $phpMaxMb) ->set('siteLogo', UploadedFile::fake()->create('logo.svg', 100, 'image/svg+xml')) ->call('saveSettings') diff --git a/tests/Feature/Settings/PasswordUpdateTest.php b/tests/Feature/Settings/PasswordUpdateTest.php index 22190be..0baec7d 100644 --- a/tests/Feature/Settings/PasswordUpdateTest.php +++ b/tests/Feature/Settings/PasswordUpdateTest.php @@ -36,4 +36,4 @@ test('correct password must be provided to update password', function () { ->call('updatePassword'); $response->assertHasErrors(['current_password']); -}); \ No newline at end of file +}); diff --git a/tests/Feature/Settings/ProfileUpdateTest.php b/tests/Feature/Settings/ProfileUpdateTest.php index 15aaf13..30624ff 100644 --- a/tests/Feature/Settings/ProfileUpdateTest.php +++ b/tests/Feature/Settings/ProfileUpdateTest.php @@ -72,4 +72,4 @@ test('correct password must be provided to delete account', function () { $response->assertHasErrors(['password']); expect($user->fresh())->not->toBeNull(); -}); \ No newline at end of file +}); diff --git a/tests/Feature/Settings/TwoFactorAuthenticationTest.php b/tests/Feature/Settings/TwoFactorAuthenticationTest.php index 70feb86..29a8deb 100644 --- a/tests/Feature/Settings/TwoFactorAuthenticationTest.php +++ b/tests/Feature/Settings/TwoFactorAuthenticationTest.php @@ -67,4 +67,4 @@ test('two factor authentication disabled when confirmation abandoned between req 'two_factor_secret' => null, 'two_factor_recovery_codes' => null, ]); -}); \ No newline at end of file +}); diff --git a/tests/Feature/SetupWizardTest.php b/tests/Feature/SetupWizardTest.php index 728d036..3c84c38 100644 --- a/tests/Feature/SetupWizardTest.php +++ b/tests/Feature/SetupWizardTest.php @@ -1,5 +1,6 @@ assertRedirect(route('upload')); }); test('setup wizard creates admin user', function () { User::query()->where('is_admin', true)->delete(); - Livewire::test(\App\Livewire\SetupWizard::class) + Livewire::test(SetupWizard::class) ->set('name', 'Admin User') ->set('email', 'admin@example.com') ->set('password', 'password123') @@ -40,7 +41,7 @@ test('setup wizard creates admin user', function () { test('setup wizard validates required fields', function () { User::query()->where('is_admin', true)->delete(); - Livewire::test(\App\Livewire\SetupWizard::class) + Livewire::test(SetupWizard::class) ->set('name', '') ->set('email', '') ->set('password', '') diff --git a/tests/Feature/ShareDownloadTest.php b/tests/Feature/ShareDownloadTest.php index f218373..2c0ff60 100644 --- a/tests/Feature/ShareDownloadTest.php +++ b/tests/Feature/ShareDownloadTest.php @@ -1,5 +1,6 @@ $share]) + Livewire::test(ShareDownload::class, ['share' => $share]) ->assertSet('authenticated', false) ->set('password', 'my-password') ->call('verifyPassword') @@ -62,7 +63,7 @@ test('wrong password is rejected', function () { $share = createShareWithFile('my-password'); - Livewire::test(\App\Livewire\ShareDownload::class, ['share' => $share]) + Livewire::test(ShareDownload::class, ['share' => $share]) ->set('password', 'wrong-password') ->call('verifyPassword') ->assertSet('authenticated', false) @@ -74,7 +75,7 @@ test('non-password share shows files directly', function () { $share = createShareWithFile(); - Livewire::test(\App\Livewire\ShareDownload::class, ['share' => $share]) + Livewire::test(ShareDownload::class, ['share' => $share]) ->assertSet('authenticated', true); }); @@ -137,7 +138,7 @@ test('zip download produces a valid archive', function () { $zipPath = $response->getFile()->getPathname(); - $zip = new \ZipArchive; + $zip = new ZipArchive; $result = $zip->open($zipPath); expect($result)->toBe(true); diff --git a/tests/Pest.php b/tests/Pest.php index bdcb1bc..a374720 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -1,5 +1,9 @@ extend(Tests\TestCase::class) - ->use(Illuminate\Foundation\Testing\RefreshDatabase::class) +pest()->extend(TestCase::class) + ->use(RefreshDatabase::class) ->beforeEach(function () { // EnsureSetupComplete middleware redirects to /setup unless an admin exists. - \App\Models\User::factory()->admin()->create(['email' => 'admin-setup@test.com']); + User::factory()->admin()->create(['email' => 'admin-setup@test.com']); }) ->in('Feature'); diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php index 27f3f87..44a4f33 100644 --- a/tests/Unit/ExampleTest.php +++ b/tests/Unit/ExampleTest.php @@ -2,4 +2,4 @@ test('that true is true', function () { expect(true)->toBeTrue(); -}); \ No newline at end of file +}); diff --git a/tests/Unit/FileEncryptionServiceTest.php b/tests/Unit/FileEncryptionServiceTest.php index 52419c4..68020f5 100644 --- a/tests/Unit/FileEncryptionServiceTest.php +++ b/tests/Unit/FileEncryptionServiceTest.php @@ -1,6 +1,7 @@ service = new FileEncryptionService; @@ -119,7 +120,7 @@ test('decrypt file stream returns streamed response', function () { $response = $this->service->decryptFileStream($encryptedPath, $key, 'test.txt', 'text/plain'); - expect($response)->toBeInstanceOf(Symfony\Component\HttpFoundation\StreamedResponse::class); + expect($response)->toBeInstanceOf(StreamedResponse::class); expect($response->headers->get('Content-Type'))->toBe('text/plain'); expect($response->headers->get('Content-Disposition'))->toContain('test.txt'); }); @@ -219,7 +220,6 @@ test('wrong key on chunked file throws exception', function () { $this->service->decryptFile($encryptedPath, $wrongKey); })->throws(RuntimeException::class, 'Decryption failed'); - test('decrypt file stream with file size sets content-length header', function () { $sourcePath = $this->tempDir.'/source.txt'; $encryptedPath = $this->tempDir.'/encrypted.enc'; diff --git a/tests/Unit/ShareServiceTest.php b/tests/Unit/ShareServiceTest.php index 28b0907..b198aa8 100644 --- a/tests/Unit/ShareServiceTest.php +++ b/tests/Unit/ShareServiceTest.php @@ -4,11 +4,14 @@ use App\Models\Setting; use App\Models\Share; use App\Models\ShareFile; use App\Services\ShareService; +use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Http\UploadedFile; +use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Storage; +use Tests\TestCase; -pest()->extend(Tests\TestCase::class) - ->use(Illuminate\Foundation\Testing\RefreshDatabase::class); +pest()->extend(TestCase::class) + ->use(RefreshDatabase::class); beforeEach(function () { Storage::fake('shares'); @@ -42,7 +45,7 @@ test('create share with password does not store encryption key', function () { expect($share->password)->not->toBeNull(); expect($share->encryption_key)->toBeNull(); - expect(\Illuminate\Support\Facades\Hash::check('my-password', $share->password))->toBeTrue(); + expect(Hash::check('my-password', $share->password))->toBeTrue(); }); test('create share with options sets expiration and max downloads', function () {