pp-planer/tests/Feature/CcliTranslationPairingServiceTest.php
Thorsten Bus cd44d6289c feat(ccli): add CcliTranslationPairingService
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-10 19:02:10 +02:00

130 lines
4.9 KiB
PHP

<?php
use App\Models\Label;
use App\Models\Song;
use App\Models\SongArrangement;
use App\Models\SongArrangementLabel;
use App\Models\SongSlide;
use App\Services\CcliTranslationPairingService;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
function makeLocalSongForCcliPairing(array $labelConfig): Song
{
$song = Song::factory()->create();
$arrangement = SongArrangement::factory()->create([
'song_id' => $song->id,
'name' => 'normal',
'is_default' => true,
]);
foreach ($labelConfig as $order => $config) {
['label_name' => $labelName, 'slide_count' => $slideCount] = $config;
$label = Label::firstOrCreate(
['name' => $labelName],
['color' => '#3B82F6'],
);
SongArrangementLabel::create([
'song_arrangement_id' => $arrangement->id,
'label_id' => $label->id,
'order' => $order + 1,
]);
for ($i = 0; $i < $slideCount; $i++) {
SongSlide::create([
'label_id' => $label->id,
'order' => $i + 1,
'text_content' => "Original line $i for $labelName",
]);
}
}
return $song;
}
test('pairs matching English labels with CCLI sections', function (): void {
$song = makeLocalSongForCcliPairing([
['label_name' => 'Verse 1', 'slide_count' => 2],
['label_name' => 'Chorus', 'slide_count' => 1],
['label_name' => 'Verse 2', 'slide_count' => 2],
]);
$ccliText = "Test Song\nTest Artist\n\nVerse 1\nCCLI line 1\nCCLI line 2\n\nChorus\nCCLI chorus\n\nVerse 2\nCCLI v2 line1\nCCLI v2 line2\n\n© 2024 Test\nCCLI # 9999001";
$result = app(CcliTranslationPairingService::class)->pair($song, $ccliText);
expect($result['unmatched_labels'])->toBeEmpty('All labels should match')
->and($result['mapping'])->toHaveCount(3)
->and($result['distributed_text'])->not->toBeEmpty();
foreach ($result['mapping'] as $entry) {
expect($entry['ccli_label'])->not->toBeNull("Label {$entry['local_label']} should have a CCLI match");
}
});
test('pairs German local labels with English CCLI labels via normalization', function (): void {
$song = makeLocalSongForCcliPairing([
['label_name' => 'Strophe 1', 'slide_count' => 2],
['label_name' => 'Refrain', 'slide_count' => 2],
['label_name' => 'Strophe 2', 'slide_count' => 2],
]);
$ccliText = "Test Song\nTest Artist\n\nVerse 1\nEN line 1\nEN line 2\n\nChorus\nEN chorus 1\nEN chorus 2\n\nVerse 2\nEN v2 1\nEN v2 2\n\n© 2024 Test\nCCLI # 9999002";
$result = app(CcliTranslationPairingService::class)->pair($song, $ccliText);
expect($result['unmatched_labels'])->toBeEmpty('German labels should normalize to match English CCLI labels')
->and($result['mapping'])->toHaveCount(3)
->and($result['mapping'][0]['ccli_label'])->toBe('Verse 1')
->and($result['mapping'][1]['ccli_label'])->toBe('Chorus');
});
test('returns unmatched_labels for sections not in CCLI', function (): void {
$song = makeLocalSongForCcliPairing([
['label_name' => 'Verse 1', 'slide_count' => 2],
['label_name' => 'Chorus', 'slide_count' => 1],
['label_name' => 'Bridge', 'slide_count' => 2],
]);
$ccliText = "Test Song\nTest Artist\n\nVerse 1\nLine 1\nLine 2\n\nChorus\nChorus line\n\n© 2024 Test\nCCLI # 9999003";
$result = app(CcliTranslationPairingService::class)->pair($song, $ccliText);
expect($result['unmatched_labels'])->toContain('Bridge')
->and($result['mapping'])->toHaveCount(3)
->and($result['mapping'][2]['ccli_label'])->toBeNull()
->and($result['mapping'][2]['distributed_lines'])->toBe(['', '']);
});
test('distributes lines preserving local slide count', function (): void {
$song = makeLocalSongForCcliPairing([
['label_name' => 'Verse 1', 'slide_count' => 2],
]);
$ccliText = "Test Song\nTest Artist\n\nVerse 1\nLine 1\nLine 2\nLine 3\nLine 4\n\n© 2024\nCCLI # 9999004";
$result = app(CcliTranslationPairingService::class)->pair($song, $ccliText);
expect($result['mapping'][0]['distributed_lines'])->toHaveCount(2)
->and($result['distributed_text'])->toContain('Line 4');
});
test('uses linesTranslated from CCLI when available', function (): void {
$song = makeLocalSongForCcliPairing([
['label_name' => 'Verse 1', 'slide_count' => 2],
['label_name' => 'Chorus', 'slide_count' => 1],
]);
$ccliText = file_get_contents(base_path('tests/fixtures/ccli/english-german-side-by-side.txt'));
$result = app(CcliTranslationPairingService::class)->pair($song, $ccliText);
expect($result['song'])->toBeInstanceOf(Song::class)
->and($result['mapping'])->toBeArray()
->and($result['distributed_text'])->toContain('Deutsche Liedzeile')
->and($result['distributed_text'])->toContain('Deutscher Refrain');
});