seed(RolesAndPermissionsSeeder::class); Storage::fake('public'); }); function makeDraftWithOwner(string $status = 'draft'): array { $owner = User::factory()->create(['is_active' => true]); $owner->assignRole('customer'); $company = Company::factory()->presseecho()->create(); $owner->companies()->attach($company->id, ['role' => 'owner']); $pr = PressRelease::factory()->create([ 'user_id' => $owner->id, 'company_id' => $company->id, 'category_id' => Category::factory()->create()->id, 'portal' => $company->portal->value, 'status' => $status, ]); return compact('owner', 'company', 'pr'); } test('owner can upload a PDF attachment', function () { /** @var TestCase $this */ ['owner' => $owner, 'pr' => $pr] = makeDraftWithOwner(); $this->actingAs($owner); $file = UploadedFile::fake()->create('factsheet.pdf', 200, 'application/pdf'); LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id]) ->set('newFile', $file) ->set('newTitle', 'Factsheet Q1') ->set('newDescription', 'Wirtschaftliche Kennzahlen') ->call('upload') ->assertHasNoErrors(); $att = $pr->attachments()->first(); expect($att)->not->toBeNull(); expect($att->title)->toBe('Factsheet Q1'); expect($att->description)->toBe('Wirtschaftliche Kennzahlen'); expect($att->original_name)->toBe('factsheet.pdf'); expect($att->mime)->toBe('application/pdf'); expect($att->sort_order)->toBe(1); expect($att->disk)->toBe('public'); publicDisk()->assertExists($att->path); }); test('upload rejects executable / disallowed file types', function () { /** @var TestCase $this */ ['owner' => $owner, 'pr' => $pr] = makeDraftWithOwner(); $this->actingAs($owner); $bad = UploadedFile::fake()->create('shell.exe', 50, 'application/octet-stream'); LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id]) ->set('newFile', $bad) ->call('upload') ->assertHasErrors(['newFile']); expect($pr->attachments()->count())->toBe(0); }); test('upload rejects oversized files', function () { /** @var TestCase $this */ ['owner' => $owner, 'pr' => $pr] = makeDraftWithOwner(); $this->actingAs($owner); // 26 MB (limit ist 25 MB). $tooBig = UploadedFile::fake()->create('big.pdf', 26 * 1024, 'application/pdf'); LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id]) ->set('newFile', $tooBig) ->call('upload') ->assertHasErrors(['newFile']); expect($pr->attachments()->count())->toBe(0); }); test('sort_order increments and moveUp / moveDown swap order', function () { /** @var TestCase $this */ ['owner' => $owner, 'pr' => $pr] = makeDraftWithOwner(); $a = PressReleaseAttachment::factory()->for($pr)->create(['sort_order' => 1, 'title' => 'A']); $b = PressReleaseAttachment::factory()->for($pr)->create(['sort_order' => 2, 'title' => 'B']); $c = PressReleaseAttachment::factory()->for($pr)->create(['sort_order' => 3, 'title' => 'C']); $this->actingAs($owner); LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id]) ->call('moveUp', $c->id); expect($b->fresh()->sort_order)->toBe(3); expect($c->fresh()->sort_order)->toBe(2); LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id]) ->call('moveDown', $a->id); // Nach moveDown sind a und c gleich (jeweils sort=2 nach swap). Es wird der Vorgänger getauscht — also a tauscht mit c (new neighbour). // Wir prüfen pragmatisch: a steht nicht mehr auf 1. expect($a->fresh()->sort_order)->not->toBe(1); }); test('remove deletes the row and the file', function () { /** @var TestCase $this */ ['owner' => $owner, 'pr' => $pr] = makeDraftWithOwner(); $this->actingAs($owner); $file = UploadedFile::fake()->create('toDelete.pdf', 50, 'application/pdf'); $component = LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id]) ->set('newFile', $file) ->call('upload') ->assertHasNoErrors(); $att = $pr->attachments()->first(); publicDisk()->assertExists($att->path); $component->call('remove', $att->id); expect($pr->attachments()->withTrashed()->find($att->id)?->trashed())->toBeTrue(); publicDisk()->assertMissing($att->path); }); test('startEdit + updateAttachment update title and description', function () { /** @var TestCase $this */ ['owner' => $owner, 'pr' => $pr] = makeDraftWithOwner(); $att = PressReleaseAttachment::factory()->for($pr)->create([ 'title' => 'Alt', 'description' => 'Alte Beschreibung', ]); $this->actingAs($owner); LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id]) ->call('startEdit', $att->id) ->assertSet('editingId', $att->id) ->assertSet('editTitle', 'Alt') ->set('editTitle', 'Neuer Titel') ->set('editDescription', 'Frische Beschreibung') ->call('updateAttachment') ->assertHasNoErrors() ->assertSet('editingId', null); $att->refresh(); expect($att->title)->toBe('Neuer Titel'); expect($att->description)->toBe('Frische Beschreibung'); }); test('foreign customer cannot upload attachments', function () { /** @var TestCase $this */ ['pr' => $pr] = makeDraftWithOwner(); $stranger = User::factory()->create(['is_active' => true]); $stranger->assignRole('customer'); $this->actingAs($stranger); $file = UploadedFile::fake()->create('any.pdf', 30, 'application/pdf'); try { LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id]) ->set('newFile', $file) ->call('upload'); } catch (AuthorizationException) { // Erwartet: Policy verhindert den Upload. } expect($pr->attachments()->count())->toBe(0); }); test('attachments cannot be uploaded when PR is published', function () { /** @var TestCase $this */ ['owner' => $owner, 'pr' => $pr] = makeDraftWithOwner('published'); $this->actingAs($owner); $file = UploadedFile::fake()->create('any.pdf', 30, 'application/pdf'); try { LivewireVolt::test('components.press-release-attachments-manager', ['pressReleaseId' => $pr->id]) ->set('newFile', $file) ->call('upload'); } catch (AuthorizationException) { // Erwartet: Customer darf an published PR nichts ändern. } expect($pr->attachments()->count())->toBe(0); });