- 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>
119 lines
4.3 KiB
PHP
119 lines
4.3 KiB
PHP
<?php
|
|
|
|
use App\Models\User;
|
|
use Laravel\Passport\Passport;
|
|
|
|
beforeEach(function () {
|
|
$this->user = User::factory()->create();
|
|
Passport::actingAs($this->user);
|
|
});
|
|
|
|
test('a preset that is not an object is rejected', function () {
|
|
// `presets: [null]` used to satisfy the bare `array` rule and then blew up
|
|
// in the frontend when it read `preset.id`.
|
|
$this->putJson('/api/settings', [
|
|
'settings' => ['presets' => [null]],
|
|
])->assertStatus(422)->assertJsonValidationErrors('settings.presets.0');
|
|
});
|
|
|
|
test('a preset without an id or name is rejected', function () {
|
|
$this->putJson('/api/settings', [
|
|
'settings' => ['presets' => [['settings' => []]]],
|
|
])->assertStatus(422)
|
|
->assertJsonValidationErrors(['settings.presets.0.id', 'settings.presets.0.name']);
|
|
});
|
|
|
|
test('a valid preset is accepted', function () {
|
|
$this->putJson('/api/settings', [
|
|
'settings' => [
|
|
'presets' => [[
|
|
'id' => 'e0a1b2c3',
|
|
'name' => 'Nachtmodus',
|
|
'updatedAt' => 1726560000000,
|
|
'settings' => ['floatingLines' => ['speed' => 1.5, 'lineMode' => 'glow']],
|
|
]],
|
|
'activePresetId' => 'e0a1b2c3',
|
|
],
|
|
])->assertOk()->assertJsonPath('data.activePresetId', 'e0a1b2c3');
|
|
});
|
|
|
|
test('a preset keeps every snapshot field when stored', function () {
|
|
// Nested rules make validated() drop any key without an explicit rule, so a
|
|
// preset used to come back as `{floatingLines: ...}` only.
|
|
$snapshot = [
|
|
'theme' => 'dark',
|
|
'appearance' => 'dark',
|
|
'accentColor' => 'coral',
|
|
'language' => 'en',
|
|
'emotionGradientStart' => '#112233',
|
|
'emotionGradientEnd' => '#445566',
|
|
'timelineZoom' => 1.75,
|
|
'showFps' => true,
|
|
'floatingLines' => ['speed' => 1.5, 'lineMode' => 'glow'],
|
|
];
|
|
|
|
$this->putJson('/api/settings', [
|
|
'settings' => [
|
|
'presets' => [['id' => 'p1', 'name' => 'Nachtmodus', 'updatedAt' => 1726560000000, 'settings' => $snapshot]],
|
|
],
|
|
])->assertOk();
|
|
|
|
expect($this->user->fresh()->settings->settings['presets'][0]['settings'])->toBe($snapshot);
|
|
});
|
|
|
|
test('unknown top level settings keys are rejected instead of stored', function () {
|
|
$this->putJson('/api/settings', [
|
|
'settings' => ['theme' => 'dark', 'isAdmin' => true],
|
|
])->assertStatus(422)->assertJsonValidationErrors('settings');
|
|
|
|
expect($this->user->fresh()->settings)->toBeNull();
|
|
});
|
|
|
|
test('unknown floating line keys are rejected', function () {
|
|
$this->putJson('/api/settings', [
|
|
'settings' => ['floatingLines' => ['speed' => 1.0, 'somethingElse' => 5]],
|
|
])->assertStatus(422)->assertJsonValidationErrors('settings.floatingLines');
|
|
});
|
|
|
|
test('a nested structure inside floating lines is rejected', function () {
|
|
$this->putJson('/api/settings', [
|
|
'settings' => ['floatingLines' => ['speed' => ['nested' => 'payload']]],
|
|
])->assertStatus(422)->assertJsonValidationErrors('settings.floatingLines.speed');
|
|
});
|
|
|
|
test('an oversized string inside floating lines is rejected', function () {
|
|
$this->putJson('/api/settings', [
|
|
'settings' => ['floatingLines' => ['gradientStops' => str_repeat('x', 5000)]],
|
|
])->assertStatus(422)->assertJsonValidationErrors('settings.floatingLines.gradientStops');
|
|
});
|
|
|
|
test('more presets than the schema allows are rejected', function () {
|
|
$presets = collect(range(1, 51))->map(fn (int $n) => [
|
|
'id' => "preset-{$n}",
|
|
'name' => "Preset {$n}",
|
|
'settings' => [],
|
|
])->all();
|
|
|
|
$this->putJson('/api/settings', ['settings' => ['presets' => $presets]])
|
|
->assertStatus(422)
|
|
->assertJsonValidationErrors('settings.presets');
|
|
});
|
|
|
|
test('known floating line values still pass through unchanged', function () {
|
|
$this->putJson('/api/settings', [
|
|
'settings' => [
|
|
'theme' => 'dark',
|
|
'language' => 'de',
|
|
'timelineZoom' => 2.5,
|
|
'showFps' => false,
|
|
'floatingLines' => [
|
|
'speed' => 1.25,
|
|
'lineCount' => 10,
|
|
'bgCenter' => '#0a0514',
|
|
'lineMode' => 'glow',
|
|
],
|
|
],
|
|
])->assertOk()
|
|
->assertJsonPath('data.floatingLines.speed', 1.25)
|
|
->assertJsonPath('data.floatingLines.lineMode', 'glow');
|
|
});
|