pp-planer/tests/Feature/AgendaItemDownloadTest.php
Thorsten Bus 0e3c647cfc feat: probundle export with media, image upscaling, upload dimension warnings
- Fix probundle exports missing images (double slides/ prefix in storage path)
- Replace manual ZipArchive with PresentationBundle + ProBundleWriter from parser plugin
- Add per-agenda-item download route and buttons for songs and slide items
- Remove text layer from image-only slides in .pro generation
- Fix image conversion: upscale small images, black bars on 2 sides max (contain)
- Add upload warnings for non-16:9 and sub-1920x1080 images (German, non-blocking)
- Update SlideFactory and all tests to use slides/ prefix in stored_filename
- Add 11 new tests (agenda download, image conversion, upload warnings)
2026-03-30 10:29:37 +02:00

233 lines
7.8 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Service;
use App\Models\ServiceAgendaItem;
use App\Models\ServiceSong;
use App\Models\Slide;
use App\Models\Song;
use App\Models\SongGroup;
use App\Models\SongSlide;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Tests\TestCase;
use ZipArchive;
final class AgendaItemDownloadTest extends TestCase
{
use RefreshDatabase;
public function test_agenda_item_mit_slides_liefert_probundle(): void
{
Storage::fake('public');
$user = User::factory()->create();
$service = Service::factory()->create();
$agendaItem = ServiceAgendaItem::factory()->create([
'service_id' => $service->id,
'title' => 'Predigt',
'sort_order' => 1,
'service_song_id' => null,
'is_before_event' => false,
]);
Storage::disk('public')->put('slides/predigt-1.jpg', 'fake-image-1');
Storage::disk('public')->put('slides/predigt-2.jpg', 'fake-image-2');
Slide::factory()->create([
'service_id' => $service->id,
'service_agenda_item_id' => $agendaItem->id,
'type' => 'sermon',
'stored_filename' => 'slides/predigt-1.jpg',
'original_filename' => 'Predigt Folie 1.jpg',
'sort_order' => 0,
]);
Slide::factory()->create([
'service_id' => $service->id,
'service_agenda_item_id' => $agendaItem->id,
'type' => 'sermon',
'stored_filename' => 'slides/predigt-2.jpg',
'original_filename' => 'Predigt Folie 2.jpg',
'sort_order' => 1,
]);
$response = $this->actingAs($user)->get(
route('services.agenda-item.download', [
'service' => $service,
'agendaItem' => $agendaItem,
])
);
$response->assertOk();
$response->assertHeader('content-type', 'application/zip');
$baseResponse = $response->baseResponse;
$this->assertInstanceOf(BinaryFileResponse::class, $baseResponse);
$copiedPath = sys_get_temp_dir().'/agenda-test-'.uniqid().'.probundle';
copy($baseResponse->getFile()->getPathname(), $copiedPath);
$zip = new ZipArchive;
$this->assertTrue($zip->open($copiedPath) === true);
$this->assertSame(3, $zip->numFiles);
$names = [];
for ($i = 0; $i < $zip->numFiles; $i++) {
$name = $zip->getNameIndex($i);
if ($name !== false) {
$names[] = $name;
}
}
$proEntry = array_values(array_filter($names, fn (string $n) => str_ends_with($n, '.pro')));
$this->assertCount(1, $proEntry, '.probundle muss genau eine .pro Datei enthalten');
$this->assertContains('predigt-1.jpg', $names);
$this->assertContains('predigt-2.jpg', $names);
$this->assertSame('fake-image-1', $zip->getFromName('predigt-1.jpg'));
$this->assertSame('fake-image-2', $zip->getFromName('predigt-2.jpg'));
$zip->close();
@unlink($copiedPath);
}
public function test_song_agenda_item_liefert_probundle_mit_pro_daten(): void
{
$user = User::factory()->create();
$service = Service::factory()->create();
$song = Song::factory()->create(['title' => 'Amazing Grace']);
$group = SongGroup::factory()->create(['song_id' => $song->id, 'name' => 'Verse 1', 'order' => 1]);
SongSlide::factory()->create(['song_group_id' => $group->id, 'text_content' => 'Amazing grace how sweet the sound', 'order' => 1]);
$serviceSong = ServiceSong::create([
'service_id' => $service->id,
'song_id' => $song->id,
'song_arrangement_id' => null,
'use_translation' => false,
'order' => 1,
'cts_song_name' => 'Amazing Grace',
'cts_ccli_id' => '12345',
]);
$agendaItem = ServiceAgendaItem::factory()->create([
'service_id' => $service->id,
'title' => 'Amazing Grace',
'sort_order' => 1,
'service_song_id' => $serviceSong->id,
'is_before_event' => false,
]);
$response = $this->actingAs($user)->get(
route('services.agenda-item.download', [
'service' => $service,
'agendaItem' => $agendaItem,
])
);
$response->assertOk();
$response->assertHeader('content-type', 'application/zip');
$baseResponse = $response->baseResponse;
$this->assertInstanceOf(BinaryFileResponse::class, $baseResponse);
$copiedPath = sys_get_temp_dir().'/agenda-song-test-'.uniqid().'.probundle';
copy($baseResponse->getFile()->getPathname(), $copiedPath);
$zip = new ZipArchive;
$this->assertTrue($zip->open($copiedPath) === true);
$this->assertSame(1, $zip->numFiles);
$this->assertTrue(str_ends_with($zip->getNameIndex(0), '.pro'), '.probundle muss eine .pro Datei enthalten');
$proContent = $zip->getFromName($zip->getNameIndex(0));
$this->assertNotFalse($proContent);
$this->assertGreaterThan(0, strlen($proContent));
$zip->close();
@unlink($copiedPath);
}
public function test_agenda_item_von_anderem_service_liefert_404(): void
{
$user = User::factory()->create();
$service = Service::factory()->create();
$otherService = Service::factory()->create();
$agendaItem = ServiceAgendaItem::factory()->create([
'service_id' => $otherService->id,
'title' => 'Fremd',
'sort_order' => 1,
'is_before_event' => false,
]);
$response = $this->actingAs($user)->get(
route('services.agenda-item.download', [
'service' => $service,
'agendaItem' => $agendaItem,
])
);
$response->assertNotFound();
}
public function test_agenda_item_ohne_slides_und_ohne_song_liefert_leeres_probundle(): void
{
$user = User::factory()->create();
$service = Service::factory()->create();
$agendaItem = ServiceAgendaItem::factory()->create([
'service_id' => $service->id,
'title' => 'Leer',
'sort_order' => 1,
'service_song_id' => null,
'is_before_event' => false,
]);
$response = $this->actingAs($user)->get(
route('services.agenda-item.download', [
'service' => $service,
'agendaItem' => $agendaItem,
])
);
$response->assertOk();
$baseResponse = $response->baseResponse;
$this->assertInstanceOf(BinaryFileResponse::class, $baseResponse);
$copiedPath = sys_get_temp_dir().'/agenda-empty-test-'.uniqid().'.probundle';
copy($baseResponse->getFile()->getPathname(), $copiedPath);
$zip = new ZipArchive;
$this->assertTrue($zip->open($copiedPath) === true);
$this->assertSame(1, $zip->numFiles);
$this->assertTrue(str_ends_with($zip->getNameIndex(0), '.pro'), 'Einziger Eintrag muss .pro Datei sein');
$zip->close();
@unlink($copiedPath);
}
public function test_erfordert_authentifizierung(): void
{
$service = Service::factory()->create();
$agendaItem = ServiceAgendaItem::factory()->create([
'service_id' => $service->id,
'is_before_event' => false,
]);
$response = $this->get(
route('services.agenda-item.download', [
'service' => $service,
'agendaItem' => $agendaItem,
])
);
$response->assertRedirect(route('login'));
}
}