- 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>
82 lines
3 KiB
PHP
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();
|
|
});
|