thats-me/backend/tests/Feature/Seeders/LifelineSeederTest.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

127 lines
5.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
use App\Models\Event;
use App\Models\User;
use Database\Seeders\DatabaseSeeder;
use Database\Seeders\LifelineSeeder;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Hash;
function seededLifelineEventCount(): int
{
return collect(LifelineSeeder::lifelineFiles())
->sum(fn (string $path): int => count(json_decode(File::get($path), true)['events']));
}
test('lifeline seeder creates jonas and sophie with a full lifeline each', function () {
$this->seed(LifelineSeeder::class);
foreach (['jonas' => 'Jonas', 'sophie' => 'Sophie'] as $handle => $name) {
$user = User::query()->where('email', "{$handle}@thats-me.app")->first();
expect($user)->not->toBeNull()
->and($user->name)->toBe($name)
->and(Hash::check('pass', $user->password))->toBeTrue()
->and($user->email_verified_at)->not->toBeNull();
$events = Event::query()->where('user_id', $user->id)->orderBy('date')->get();
// 21 years (25–45) × 12 months, one event per month
expect($events)->toHaveCount(252)
->and($events->first()->date)->toStartWith('2005-01-')
->and($events->last()->date)->toStartWith('2025-12-')
->and($events->pluck('date')->map(fn (string $date): string => substr($date, 0, 7))->unique())->toHaveCount(252);
foreach ($events as $event) {
expect($event->title)->not->toBe('')
->and(mb_strlen($event->title))->toBeLessThanOrEqual(40)
->and($event->note)->not->toBe('')
->and((float) $event->emotion)->toBeGreaterThanOrEqual(-1)
->and((float) $event->emotion)->toBeLessThanOrEqual(1)
->and($event->image)->toBeNull();
}
}
});
test('lifeline seeder maps ratings onto the emotion bands of the event panel', function () {
$this->seed(LifelineSeeder::class);
$jonas = User::query()->where('email', 'jonas@thats-me.app')->firstOrFail();
$emotionFor = fn (int $age, int $month): float => (float) Event::query()
->where('client_id', LifelineSeeder::clientId('jonas@thats-me.app', $age, $month))
->where('user_id', $jonas->id)
->value('emotion');
// 25 / Juli: Lagerfeuer in Slowenien → Sehr positiv
expect($emotionFor(25, 7))->toBeGreaterThan(0.5);
// 25 / Januar: Erstes eigenes Projekt → Positiv
expect($emotionFor(25, 1))->toBeGreaterThan(0.15)->toBeLessThanOrEqual(0.5);
// 25 / Februar: Sonntags-Vorkochen → Neutral
expect($emotionFor(25, 2))->toBeGreaterThan(-0.15)->toBeLessThanOrEqual(0.15);
// 25 / Juni: Spätschicht im Büro → Negativ
expect($emotionFor(25, 6))->toBeGreaterThan(-0.5)->toBeLessThanOrEqual(-0.15);
// 40 / Mai: Letztes Foto von Thomas → Sehr negativ
expect($emotionFor(40, 5))->toBeLessThanOrEqual(-0.5);
});
test('lifeline seeder is idempotent and keeps manually imported key images', function () {
$this->seed(LifelineSeeder::class);
$clientId = LifelineSeeder::clientId('sophie@thats-me.app', 35, 1);
$event = Event::query()->where('client_id', $clientId)->firstOrFail();
$event->update([
'image' => 'media/sophie/clara.jpg',
'key_image_title' => 'Clara',
'title' => 'Manuell geändert',
]);
$this->seed(LifelineSeeder::class);
$event->refresh();
expect(Event::query()->count())->toBe(seededLifelineEventCount())
->and(User::query()->where('email', 'like', '%@thats-me.app')->count())->toBe(count(LifelineSeeder::lifelineFiles()))
->and($event->image)->toBe('media/sophie/clara.jpg')
->and($event->key_image_title)->toBe('Clara')
->and($event->title)->toBe('Clara ist da');
});
test('database seeder includes the lifelines', function () {
$this->seed(DatabaseSeeder::class);
foreach (['jonas', 'sophie', 'nadja', 'werner', 'bruno', 'benz', 'lukas', 'nachtschicht'] as $handle) {
expect(User::query()->where('email', "{$handle}@thats-me.app")->exists())->toBeTrue();
}
expect(Event::query()->count())->toBe(seededLifelineEventCount());
});
test('additional lifelines have gaps and end at their final year', function () {
$this->seed(LifelineSeeder::class);
$range = function (string $handle): array {
$user = User::query()->where('email', "{$handle}@thats-me.app")->firstOrFail();
$events = Event::query()->where('user_id', $user->id);
return [$events->count(), substr($events->min('date'), 0, 4), substr($events->max('date'), 0, 4)];
};
// Nadja: 1988 + 24 … 37, Werner: 1955 + 55 … 70, Bruno: 2014 + 0 … 10
expect($range('nadja'))->toBe([42, '2012', '2025'])
->and($range('werner'))->toBe([41, '2010', '2025'])
->and($range('bruno'))->toBe([35, '2014', '2024'])
->and($range('benz'))->toBe([37, '1981', '2025'])
->and($range('lukas'))->toBe([69, '2012', '2025'])
->and($range('nachtschicht'))->toBe([57, '1995', '2025']);
});
test('an explicit day separates several events in the same month', function () {
$email = 'nadja@thats-me.app';
expect(LifelineSeeder::clientId($email, 32, 5))->not->toBe(LifelineSeeder::clientId($email, 32, 5, 12))
->and(LifelineSeeder::clientId($email, 32, 5, 12))->not->toBe(LifelineSeeder::clientId($email, 32, 5, 27))
->and(LifelineSeeder::date(1988, 32, 5, $email, 12))->toBe('2020-05-12')
->and(LifelineSeeder::date(1988, 32, 5, $email))->toMatch('/^2020-05-(0[1-9]|1\d|2[0-8])$/');
});