thats-me/backend/tests/Feature/Console/ImportLifelineImagesTest.php
Kevin Adametz b262a6771f Backend: Medien-Typen, Sprachnotizen, Event-Felder und Sync-Härtung
- Event-Medien tragen Typ (image/video/audio), Dauer und Notiz; Videos
  bekommen ein Poster, Audio wird per ffmpeg nach AAC (audio/mp4)
  gewandelt, Originale werden mit Range-Unterstützung gestreamt.
- Upload nimmt eine Notiz (caption) direkt mit, PATCH ändert sie später.
- Neue Event-Felder: Zeitraum (end_date), Kategorie, Personen, Meilenstein.
  Regeln zentral in EventFields, damit CRUD und Sync gleich prüfen.
- Titel darf leer sein: ein Event entsteht mit dem ersten Eintrag.
- Artisan-Befehl media:transcode-voice-notes wandelt alte WebM/Ogg-Notizen,
  die Safari nicht abspielen kann.
- Login-Throttling, Settings-Validierung, Sync-Fehlercodes ohne Rohtext.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-25 07:00:35 +00:00

82 lines
3 KiB
PHP

<?php
use App\Models\Event;
use App\Models\User;
use Database\Seeders\LifelineSeeder;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
beforeEach(function () {
Storage::fake('local');
$this->imageDirectory = sys_get_temp_dir().'/lifeline-images-test-'.uniqid();
File::ensureDirectoryExists($this->imageDirectory);
});
afterEach(function () {
File::deleteDirectory($this->imageDirectory);
});
function fakeLifelineImage(string $path): void
{
$image = imagecreatetruecolor(64, 48);
imagefill($image, 0, 0, imagecolorallocate($image, 200, 160, 90));
imagepng($image, $path);
imagedestroy($image);
}
test('import attaches key images to seeded lifeline events by age and month', function () {
$this->seed(LifelineSeeder::class);
foreach (['04-07', '10-02'] as $slug) {
fakeLifelineImage("{$this->imageDirectory}/{$slug}.png");
}
$this->artisan('lifelines:import-images', ['handle' => 'bruno', '--from' => $this->imageDirectory])
->expectsOutputToContain('Imported 2, skipped 0')
->assertSuccessful();
$bruno = User::query()->where('email', 'bruno@thats-me.app')->firstOrFail();
$event = Event::query()
->where('client_id', LifelineSeeder::clientId('bruno@thats-me.app', 4, 7))
->firstOrFail();
$media = $event->media()->where('collection', 'key_image')->firstOrFail();
expect($event->image)->toBe("/event-media/{$media->id}/thumb")
->and($media->user_id)->toBe($bruno->id)
->and($media->mime_type)->toBe('image/jpeg');
Storage::disk('local')->assertExists([$media->path, $media->preview_path, $media->thumbnail_path]);
});
test('import skips events that already have a key image unless forced', function () {
$this->seed(LifelineSeeder::class);
fakeLifelineImage("{$this->imageDirectory}/04-07.png");
$this->artisan('lifelines:import-images', ['handle' => 'bruno', '--from' => $this->imageDirectory])->assertSuccessful();
$this->artisan('lifelines:import-images', ['handle' => 'bruno', '--from' => $this->imageDirectory])
->expectsOutputToContain('Imported 0, skipped 1')
->assertSuccessful();
$event = Event::query()
->where('client_id', LifelineSeeder::clientId('bruno@thats-me.app', 4, 7))
->firstOrFail();
$firstMedia = $event->media()->where('collection', 'key_image')->firstOrFail();
$this->artisan('lifelines:import-images', ['handle' => 'bruno', '--from' => $this->imageDirectory, '--force' => true])
->expectsOutputToContain('Imported 1')
->assertSuccessful();
expect($event->media()->where('collection', 'key_image')->count())->toBe(1)
->and($event->media()->where('collection', 'key_image')->first()->id)->not->toBe($firstMedia->id);
Storage::disk('local')->assertMissing($firstMedia->path);
});
test('import fails when the lifeline user is not seeded', function () {
$this->artisan('lifelines:import-images', ['handle' => 'bruno', '--from' => $this->imageDirectory])
->expectsOutputToContain('not seeded yet')
->assertFailed();
});