pp-planer/tests/Feature/ExportProFileKeywordInjectionTest.php

167 lines
5.7 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\ExportProFile;
use App\Models\Label;
use App\Models\Service;
use App\Models\ServiceAgendaItem;
use App\Models\ServiceSong;
use App\Models\Song;
use App\Services\PlaylistExportService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;
use ProPresenter\Parser\ProPlaylistReader;
use Tests\TestCase;
final class ExportProFileKeywordInjectionTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
Storage::fake('local');
Storage::fake('public');
}
private function createSongWithContent(string $title): Song
{
$song = Song::create([
'title' => $title,
'ccli_id' => fake()->unique()->numerify('#####'),
'author' => 'Test Author',
'copyright_text' => 'Test Publisher',
]);
$label = Label::firstOrCreate(
['name' => 'Verse - '.$title],
['color' => '#2196F3'],
);
$section = $song->sections()->create(['label_id' => $label->id, 'order' => 0]);
$section->slides()->create(['order' => 0, 'text_content' => 'Erste Zeile']);
$arrangement = $song->arrangements()->create(['name' => 'normal', 'is_default' => true]);
$arrangement->arrangementSections()->create(['song_section_id' => $section->id, 'order' => 0]);
return $song;
}
private function addSongAgendaItem(Service $service, Song $song, string $title, int $sortOrder): void
{
$serviceSong = ServiceSong::create([
'service_id' => $service->id,
'song_id' => $song->id,
'cts_song_name' => $song->title,
'order' => $sortOrder,
]);
ServiceAgendaItem::factory()->create([
'service_id' => $service->id,
'title' => $title,
'service_song_id' => $serviceSong->id,
'sort_order' => $sortOrder,
'is_before_event' => false,
]);
}
private function cleanupTempDir(string $dir): void
{
if (! is_dir($dir)) {
return;
}
$items = scandir($dir);
if ($items === false) {
return;
}
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$path = $dir.'/'.$item;
is_dir($path) ? $this->cleanupTempDir($path) : unlink($path);
}
rmdir($dir);
}
public function test_keyword_datei_wird_direkt_nach_passendem_ablaufpunkt_eingefuegt(): void
{
$service = Service::factory()->create([
'title' => 'Keyword Service',
'date' => now(),
]);
// Uppercase title vs lowercase keyword proves case-insensitive substring match.
$this->addSongAgendaItem($service, $this->createSongWithContent('Abendmahlslied'), 'ABENDMAHL feiern', 1);
$this->addSongAgendaItem($service, $this->createSongWithContent('Predigtlied'), 'Predigt', 2);
$storedPath = 'export-pro-files/keyword/kommunion.pro';
Storage::disk('local')->put($storedPath, 'kommunion-pro-bytes');
ExportProFile::create([
'type' => 'keyword',
'keyword' => 'abendmahl',
'original_name' => 'kommunion.pro',
'stored_path' => $storedPath,
'order' => 1,
]);
$result = app(PlaylistExportService::class)->generatePlaylist($service);
$playlist = ProPlaylistReader::read($result['path']);
$entryNames = array_map(fn ($e) => $e->getName(), $playlist->getEntries());
$abendmahlPos = array_search('Abendmahlslied', $entryNames, true);
$predigtPos = array_search('Predigtlied', $entryNames, true);
$kommunionPositions = array_keys($entryNames, 'kommunion', true);
$this->assertNotFalse($abendmahlPos, 'Abendmahl song entry not found');
$this->assertNotFalse($predigtPos, 'Predigt song entry not found');
$this->assertCount(1, $kommunionPositions, 'Keyword presentation must be injected exactly once');
$kommunionPos = $kommunionPositions[0];
$this->assertSame($abendmahlPos + 1, $kommunionPos, 'Keyword presentation must appear immediately after the matched item');
$this->assertLessThan($predigtPos, $kommunionPos, 'Keyword presentation must not appear after the non-matching item');
$embeddedFiles = $playlist->getEmbeddedFiles();
$this->assertArrayHasKey('KEYWORD_1_kommunion.pro', $embeddedFiles);
$this->assertSame('kommunion-pro-bytes', $embeddedFiles['KEYWORD_1_kommunion.pro']);
$this->cleanupTempDir($result['temp_dir']);
}
public function test_leeres_keyword_wird_nicht_injiziert(): void
{
$service = Service::factory()->create([
'title' => 'Kein Keyword Service',
'date' => now(),
]);
$this->addSongAgendaItem($service, $this->createSongWithContent('Abendmahlslied'), 'Abendmahl feiern', 1);
$storedPath = 'export-pro-files/keyword/leer.pro';
Storage::disk('local')->put($storedPath, 'leer-pro-bytes');
ExportProFile::create([
'type' => 'keyword',
'keyword' => '',
'original_name' => 'leer.pro',
'stored_path' => $storedPath,
'order' => 1,
]);
$result = app(PlaylistExportService::class)->generatePlaylist($service);
$playlist = ProPlaylistReader::read($result['path']);
$entryNames = array_map(fn ($e) => $e->getName(), $playlist->getEntries());
$this->assertNotContains('leer', $entryNames, 'Files with an empty keyword must never be injected');
$this->cleanupTempDir($result['temp_dir']);
}
}