Auto-formatted by Laravel Pint (default Laravel preset): string concatenation spacing, anonymous class brace placement, constructor body shorthand, import ordering, and assertion indentation.
232 lines
6.6 KiB
PHP
232 lines
6.6 KiB
PHP
<?php
|
|
|
|
use App\Services\ChurchToolsService;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
beforeEach(function () {
|
|
ensureSyncTables();
|
|
});
|
|
|
|
test('cts:sync synchronisiert services, agenda songs und schreibt sync log', function () {
|
|
Carbon::setTestNow('2026-03-01 09:00:00');
|
|
|
|
$localSongId = DB::table('songs')->insertGetId([
|
|
'title' => 'Way Maker',
|
|
'ccli_id' => '7115744',
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
|
eventFetcher: fn () => [
|
|
new FakeEvent(
|
|
id: 100,
|
|
title: 'Gottesdienst Sonntag',
|
|
startDate: '2026-03-08T10:00:00+00:00',
|
|
note: 'Probe',
|
|
eventServices: [
|
|
new FakeEventService('Predigt', new FakePerson('Max', 'Mustermann')),
|
|
new FakeEventService('Beamer', new FakePerson('Lisa', 'Technik')),
|
|
],
|
|
),
|
|
],
|
|
songFetcher: fn () => [new FakeSong(id: 1, title: 'Way Maker', ccli: '7115744')],
|
|
agendaFetcher: fn () => new FakeAgenda([
|
|
new FakeSong(id: 5001, title: 'Way Maker', ccli: '7115744'),
|
|
new FakeSong(id: 5002, title: 'Unbekannt', ccli: '9999999'),
|
|
]),
|
|
eventServiceFetcher: fn (int $eventId) => [
|
|
new FakeEventService('Predigt', new FakePerson('Max', 'Mustermann')),
|
|
new FakeEventService('Beamer', new FakePerson('Lisa', 'Technik')),
|
|
],
|
|
));
|
|
|
|
Artisan::call('cts:sync');
|
|
|
|
expect(Artisan::output())->toContain('Daten wurden aktualisiert');
|
|
|
|
$service = DB::table('services')->where('cts_event_id', '100')->first();
|
|
expect($service)->not->toBeNull();
|
|
expect($service->title)->toBe('Gottesdienst Sonntag');
|
|
expect($service->preacher_name)->toBe('Max Mustermann');
|
|
expect($service->beamer_tech_name)->toBe('Lisa Technik');
|
|
|
|
$matchedSong = DB::table('service_songs')->where('order', 1)->first();
|
|
expect($matchedSong)->not->toBeNull();
|
|
expect((int) $matchedSong->song_id)->toBe($localSongId);
|
|
expect($matchedSong->matched_at)->not->toBeNull();
|
|
|
|
$unmatchedSong = DB::table('service_songs')->where('order', 2)->first();
|
|
expect($unmatchedSong)->not->toBeNull();
|
|
expect($unmatchedSong->song_id)->toBeNull();
|
|
expect($unmatchedSong->cts_ccli_id)->toBe('9999999');
|
|
|
|
$syncLog = DB::table('cts_sync_log')->latest('id')->first();
|
|
expect($syncLog)->not->toBeNull();
|
|
expect($syncLog->status)->toBe('success');
|
|
expect((int) $syncLog->events_count)->toBe(1);
|
|
expect((int) $syncLog->songs_count)->toBe(2);
|
|
});
|
|
|
|
function ensureSyncTables(): void
|
|
{
|
|
if (! Schema::hasTable('services')) {
|
|
Schema::create('services', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('cts_event_id')->unique();
|
|
$table->string('title');
|
|
$table->date('date')->nullable();
|
|
$table->string('preacher_name')->nullable();
|
|
$table->string('beamer_tech_name')->nullable();
|
|
$table->timestamp('last_synced_at')->nullable();
|
|
$table->json('cts_data')->nullable();
|
|
$table->timestamps();
|
|
});
|
|
}
|
|
|
|
if (! Schema::hasTable('songs')) {
|
|
Schema::create('songs', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('title');
|
|
$table->string('ccli_id')->nullable()->unique();
|
|
$table->timestamps();
|
|
});
|
|
}
|
|
|
|
if (! Schema::hasTable('service_songs')) {
|
|
Schema::create('service_songs', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->foreignId('service_id')->constrained('services')->cascadeOnDelete();
|
|
$table->foreignId('song_id')->nullable()->constrained('songs')->nullOnDelete();
|
|
$table->string('cts_song_name');
|
|
$table->string('cts_ccli_id')->nullable();
|
|
$table->unsignedInteger('order')->default(0);
|
|
$table->timestamp('matched_at')->nullable();
|
|
$table->timestamps();
|
|
$table->unique(['service_id', 'order']);
|
|
});
|
|
}
|
|
|
|
if (! Schema::hasTable('cts_sync_log')) {
|
|
Schema::create('cts_sync_log', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->timestamp('synced_at')->nullable();
|
|
$table->unsignedInteger('events_count')->default(0);
|
|
$table->unsignedInteger('songs_count')->default(0);
|
|
$table->string('status');
|
|
$table->text('error')->nullable();
|
|
$table->timestamps();
|
|
});
|
|
}
|
|
}
|
|
|
|
final class FakeEvent
|
|
{
|
|
public function __construct(
|
|
private readonly int $id,
|
|
private readonly string $title,
|
|
private readonly string $startDate,
|
|
private readonly ?string $note = null,
|
|
private readonly array $eventServices = [],
|
|
) {}
|
|
|
|
public function getId(): string
|
|
{
|
|
return (string) $this->id;
|
|
}
|
|
|
|
public function getName(): string
|
|
{
|
|
return $this->title;
|
|
}
|
|
|
|
public function getStartDate(): string
|
|
{
|
|
return $this->startDate;
|
|
}
|
|
|
|
public function getNote(): ?string
|
|
{
|
|
return $this->note;
|
|
}
|
|
|
|
public function getEventServices(): array
|
|
{
|
|
return $this->eventServices;
|
|
}
|
|
}
|
|
|
|
final class FakeEventService
|
|
{
|
|
public function __construct(
|
|
private readonly string $name,
|
|
private readonly ?FakePerson $person,
|
|
) {}
|
|
|
|
public function getName(): string
|
|
{
|
|
return $this->name;
|
|
}
|
|
|
|
public function getPerson(): ?FakePerson
|
|
{
|
|
return $this->person;
|
|
}
|
|
}
|
|
|
|
final class FakePerson
|
|
{
|
|
public function __construct(
|
|
private readonly string $firstName,
|
|
private readonly string $lastName,
|
|
) {}
|
|
|
|
public function getFirstName(): string
|
|
{
|
|
return $this->firstName;
|
|
}
|
|
|
|
public function getLastName(): string
|
|
{
|
|
return $this->lastName;
|
|
}
|
|
}
|
|
|
|
final class FakeAgenda
|
|
{
|
|
public function __construct(private readonly array $songs) {}
|
|
|
|
public function getSongs(): array
|
|
{
|
|
return $this->songs;
|
|
}
|
|
}
|
|
|
|
final class FakeSong
|
|
{
|
|
public function __construct(
|
|
private readonly int $id,
|
|
private readonly string $title,
|
|
private readonly ?string $ccli,
|
|
) {}
|
|
|
|
public function getId(): string
|
|
{
|
|
return (string) $this->id;
|
|
}
|
|
|
|
public function getName(): string
|
|
{
|
|
return $this->title;
|
|
}
|
|
|
|
public function getCcli(): ?string
|
|
{
|
|
return $this->ccli;
|
|
}
|
|
}
|