user = User::factory()->create(); }); // --- Modal Data Loading --- test('show returns song with full detail for modal', function () { $song = Song::factory()->create([ 'title' => 'Großer Gott wir loben Dich', 'ccli_id' => '100200', 'copyright_text' => '© Public Domain', ]); $group1 = SongGroup::factory()->create([ 'song_id' => $song->id, 'name' => 'Strophe 1', 'color' => '#3B82F6', 'order' => 1, ]); $group2 = SongGroup::factory()->create([ 'song_id' => $song->id, 'name' => 'Refrain', 'color' => '#10B981', 'order' => 2, ]); $arrangement = SongArrangement::factory()->create([ 'song_id' => $song->id, 'name' => 'Normal', 'is_default' => true, ]); SongArrangementGroup::factory()->create([ 'song_arrangement_id' => $arrangement->id, 'song_group_id' => $group1->id, 'order' => 1, ]); SongArrangementGroup::factory()->create([ 'song_arrangement_id' => $arrangement->id, 'song_group_id' => $group2->id, 'order' => 2, ]); $response = $this->actingAs($this->user) ->getJson("/api/songs/{$song->id}"); $response->assertOk() ->assertJsonPath('data.title', 'Großer Gott wir loben Dich') ->assertJsonPath('data.ccli_id', '100200') ->assertJsonPath('data.copyright_text', '© Public Domain') ->assertJsonStructure([ 'data' => [ 'id', 'title', 'ccli_id', 'copyright_text', 'groups' => [['id', 'name', 'color', 'order', 'slides']], 'arrangements' => [['id', 'name', 'is_default', 'arrangement_groups']], ], ]); // Groups in correct order expect($response->json('data.groups.0.name'))->toBe('Strophe 1'); expect($response->json('data.groups.1.name'))->toBe('Refrain'); // Arrangement with arrangement_groups expect($response->json('data.arrangements.0.name'))->toBe('Normal'); expect($response->json('data.arrangements.0.arrangement_groups'))->toHaveCount(2); }); // --- Metadata Auto-Save --- test('update saves title via auto-save', function () { $song = Song::factory()->create(['title' => 'Original Title']); $response = $this->actingAs($this->user) ->putJson("/api/songs/{$song->id}", [ 'title' => 'Neuer Titel', ]); $response->assertOk() ->assertJsonFragment(['message' => 'Song erfolgreich aktualisiert']); $song->refresh(); expect($song->title)->toBe('Neuer Titel'); }); test('update saves ccli_id via auto-save', function () { $song = Song::factory()->create(['ccli_id' => '111111']); $response = $this->actingAs($this->user) ->putJson("/api/songs/{$song->id}", [ 'title' => $song->title, 'ccli_id' => '999888', ]); $response->assertOk(); $song->refresh(); expect($song->ccli_id)->toBe('999888'); }); test('update saves copyright_text via auto-save', function () { $song = Song::factory()->create(['copyright_text' => null]); $response = $this->actingAs($this->user) ->putJson("/api/songs/{$song->id}", [ 'title' => $song->title, 'copyright_text' => '© 2024 Neuer Copyright-Text', ]); $response->assertOk(); $song->refresh(); expect($song->copyright_text)->toBe('© 2024 Neuer Copyright-Text'); }); test('update can clear optional fields with null', function () { $song = Song::factory()->create([ 'ccli_id' => '555555', 'copyright_text' => 'Some copyright', ]); $response = $this->actingAs($this->user) ->putJson("/api/songs/{$song->id}", [ 'title' => $song->title, 'ccli_id' => null, 'copyright_text' => null, ]); $response->assertOk(); $song->refresh(); expect($song->ccli_id)->toBeNull(); expect($song->copyright_text)->toBeNull(); }); test('update returns full song detail with arrangements', function () { $song = Song::factory()->create(); SongGroup::factory()->create(['song_id' => $song->id]); SongArrangement::factory()->create(['song_id' => $song->id, 'is_default' => true]); $response = $this->actingAs($this->user) ->putJson("/api/songs/{$song->id}", [ 'title' => 'Updated Song', ]); $response->assertOk() ->assertJsonStructure([ 'data' => [ 'id', 'title', 'ccli_id', 'copyright_text', 'groups', 'arrangements', ], ]); }); test('update validates title is required', function () { $song = Song::factory()->create(); $response = $this->actingAs($this->user) ->putJson("/api/songs/{$song->id}", [ 'title' => '', ]); $response->assertUnprocessable() ->assertJsonValidationErrors(['title']); }); test('update validates unique ccli_id against other songs', function () { Song::factory()->create(['ccli_id' => '777777']); $song = Song::factory()->create(['ccli_id' => '888888']); $response = $this->actingAs($this->user) ->putJson("/api/songs/{$song->id}", [ 'title' => $song->title, 'ccli_id' => '777777', ]); $response->assertUnprocessable() ->assertJsonValidationErrors(['ccli_id']); }); test('update requires authentication', function () { $song = Song::factory()->create(); $response = $this->putJson("/api/songs/{$song->id}", [ 'title' => 'Should Fail', ]); $response->assertUnauthorized(); }); test('show returns 404 for soft-deleted song', function () { $song = Song::factory()->create(['deleted_at' => now()]); $response = $this->actingAs($this->user) ->getJson("/api/songs/{$song->id}"); $response->assertNotFound(); }); test('update returns 404 for nonexistent song', function () { $response = $this->actingAs($this->user) ->putJson('/api/songs/99999', [ 'title' => 'Ghost Song', ]); $response->assertNotFound(); });