create(); $perm = Permission::firstOrCreate(['name' => 'manage global settings', 'guard_name' => 'web']); $user->givePermissionTo($perm); return $user; } beforeEach(function () { Storage::fake('public'); }); // ── Access control ───────────────────────────────────────────────────────────── test('guest cannot upload editor images', function () { $this->postJson('/editor/upload')->assertUnauthorized(); }); test('user without manage global settings cannot upload', function () { $user = User::factory()->create(); $this->actingAs($user) ->postJson('/editor/upload') ->assertForbidden(); }); // ── Missing file ─────────────────────────────────────────────────────────────── test('upload without file returns 400', function () { $user = makeEditorUser(); $this->actingAs($user) ->postJson('/editor/upload') ->assertStatus(400) ->assertJsonPath('error.message', 'No file uploaded.'); }); // ── Type validation ──────────────────────────────────────────────────────────── test('upload rejects non-image file', function () { $user = makeEditorUser(); $file = UploadedFile::fake()->create('shell.php', 100, 'application/x-php'); $this->actingAs($user) ->postJson('/editor/upload', ['upload' => $file]) ->assertStatus(422); }); test('upload rejects svg (not in allowed mimes)', function () { $user = makeEditorUser(); $file = UploadedFile::fake()->create('vector.svg', 50, 'image/svg+xml'); $this->actingAs($user) ->postJson('/editor/upload', ['upload' => $file]) ->assertStatus(422); }); test('upload accepts jpeg image', function () { $user = makeEditorUser(); $file = UploadedFile::fake()->image('photo.jpg', 800, 600); $this->actingAs($user) ->post('/editor/upload', ['upload' => $file]) ->assertOk() ->assertJsonPath('uploaded', 1) ->assertJsonStructure(['url', 'fileName']); }); test('upload accepts png image', function () { $user = makeEditorUser(); $file = UploadedFile::fake()->image('icon.png', 100, 100); $this->actingAs($user) ->post('/editor/upload', ['upload' => $file]) ->assertOk() ->assertJsonPath('uploaded', 1); }); test('upload accepts webp image', function () { $user = makeEditorUser(); $file = UploadedFile::fake()->image('modern.webp', 400, 300); $this->actingAs($user) ->post('/editor/upload', ['upload' => $file]) ->assertOk() ->assertJsonPath('uploaded', 1); }); // ── Size limit ───────────────────────────────────────────────────────────────── test('upload rejects image exceeding 5 MB', function () { $user = makeEditorUser(); $file = UploadedFile::fake()->create('big.jpg', 6000, 'image/jpeg'); // 6 MB $this->actingAs($user) ->postJson('/editor/upload', ['upload' => $file]) ->assertStatus(422); }); // ── Response structure ───────────────────────────────────────────────────────── test('successful upload response has uploaded, fileName, url keys', function () { $user = makeEditorUser(); $file = UploadedFile::fake()->image('test.jpg'); $response = $this->actingAs($user) ->post('/editor/upload', ['upload' => $file]) ->assertOk() ->json(); expect($response)->toHaveKeys(['uploaded', 'fileName', 'url']); expect($response['uploaded'])->toBe(1); expect($response['url'])->toStartWith('/storage/'); }); // ── File is actually stored ──────────────────────────────────────────────────── test('uploaded file is persisted to the public disk under editor/', function () { $user = makeEditorUser(); $file = UploadedFile::fake()->image('stored.jpg'); $response = $this->actingAs($user) ->post('/editor/upload', ['upload' => $file]) ->json(); // Strip /storage/ prefix to get the relative path on the public disk $relativePath = substr((string) $response['url'], strlen('/storage/')); Storage::disk('public')->assertExists($relativePath); });