getJson('/api/events/event-id/media') ->assertUnauthorized(); }); test('can upload key image and receive protected urls', function () { Storage::fake('local'); $user = User::factory()->create(); $event = Event::factory()->create(['user_id' => $user->id]); Passport::actingAs($user); $response = $this->postJson("/api/events/{$event->client_id}/media", [ 'collection' => 'key_image', 'file' => UploadedFile::fake()->image('avatar.png', 1200, 800), ]); $response->assertCreated() ->assertJsonPath('data.collection', 'key_image') ->assertJsonPath('data.type', 'image') ->assertJsonPath('data.thumbnailWidth', 320) ->assertJsonPath('data.thumbnailHeight', 320) ->assertJsonPath('data.previewWidth', 900); $media = EventMedia::query()->firstOrFail(); Storage::disk('local')->assertExists($media->path); Storage::disk('local')->assertExists($media->preview_path); Storage::disk('local')->assertExists($media->thumbnail_path); expect($event->fresh()->image)->toBe("/event-media/{$media->id}/thumb"); }); test('uploaded originals keep a4 quality maximum side', function () { Storage::fake('local'); $user = User::factory()->create(); $event = Event::factory()->create(['user_id' => $user->id]); Passport::actingAs($user); $this->postJson("/api/events/{$event->client_id}/media", [ 'collection' => 'gallery', 'file' => UploadedFile::fake()->image('large.jpg', 5000, 2500), ])->assertCreated(); $media = EventMedia::query()->firstOrFail(); expect($media->width)->toBe(3508) ->and($media->height)->toBe(1754) ->and($media->preview_width)->toBe(900) ->and($media->preview_height)->toBe(450) ->and(max($media->width, $media->height))->toBe(3508); }); test('can list media for own event', function () { $user = User::factory()->create(); $event = Event::factory()->create(['user_id' => $user->id]); EventMedia::factory()->create([ 'user_id' => $user->id, 'event_id' => $event->id, 'collection' => 'gallery', ]); Passport::actingAs($user); $this->getJson("/api/events/{$event->client_id}/media") ->assertOk() ->assertJsonCount(1, 'data') ->assertJsonPath('data.0.collection', 'gallery'); }); test('can stream thumbnail for own media', function () { Storage::fake('local'); $user = User::factory()->create(); $event = Event::factory()->create(['user_id' => $user->id]); $media = EventMedia::factory()->create([ 'user_id' => $user->id, 'event_id' => $event->id, 'thumbnail_path' => 'event-media/test/thumb.jpg', ]); Storage::disk('local')->put($media->thumbnail_path, 'fake-jpeg'); Passport::actingAs($user); $this->get("/api/event-media/{$media->id}/thumb") ->assertOk() ->assertHeader('Content-Type', 'image/jpeg'); }); test('can stream preview for own media', function () { Storage::fake('local'); $user = User::factory()->create(); $event = Event::factory()->create(['user_id' => $user->id]); $media = EventMedia::factory()->create([ 'user_id' => $user->id, 'event_id' => $event->id, 'preview_path' => 'event-media/test/preview.jpg', ]); Storage::disk('local')->put($media->preview_path, 'fake-jpeg'); Passport::actingAs($user); $this->get("/api/event-media/{$media->id}/preview") ->assertOk() ->assertHeader('Content-Type', 'image/jpeg'); }); test('preview falls back to thumbnail for legacy media', function () { Storage::fake('local'); $user = User::factory()->create(); $event = Event::factory()->create(['user_id' => $user->id]); $media = EventMedia::factory()->create([ 'user_id' => $user->id, 'event_id' => $event->id, 'preview_path' => null, 'thumbnail_path' => 'event-media/test/thumb.jpg', ]); Storage::disk('local')->put($media->thumbnail_path, 'legacy-thumb'); Passport::actingAs($user); $this->get("/api/event-media/{$media->id}/preview") ->assertOk() ->assertHeader('Content-Type', 'image/jpeg'); }); test('cannot access another users media', function () { Storage::fake('local'); $user = User::factory()->create(); $otherUser = User::factory()->create(); $event = Event::factory()->create(['user_id' => $otherUser->id]); $media = EventMedia::factory()->create([ 'user_id' => $otherUser->id, 'event_id' => $event->id, 'thumbnail_path' => 'event-media/test/thumb.jpg', ]); Storage::disk('local')->put($media->thumbnail_path, 'fake-jpeg'); Passport::actingAs($user); $this->getJson("/api/events/{$event->client_id}/media") ->assertNotFound(); $this->get("/api/event-media/{$media->id}/thumb") ->assertNotFound(); }); test('can delete own media and files', function () { Storage::fake('local'); $user = User::factory()->create(); $event = Event::factory()->create(['user_id' => $user->id]); $media = EventMedia::factory()->create([ 'user_id' => $user->id, 'event_id' => $event->id, 'path' => 'event-media/test/original.jpg', 'preview_path' => null, 'thumbnail_path' => 'event-media/test/thumb.jpg', ]); Storage::disk('local')->put($media->path, 'original'); Storage::disk('local')->put($media->thumbnail_path, 'thumb'); Passport::actingAs($user); $this->deleteJson("/api/events/{$event->client_id}/media/{$media->id}") ->assertNoContent(); $this->assertDatabaseMissing('event_media', ['id' => $media->id]); Storage::disk('local')->assertMissing($media->path); Storage::disk('local')->assertMissing($media->thumbnail_path); });