package database import ( "database/sql" "path/filepath" "testing" ) func openLyricsTestDB(t *testing.T) (*sql.DB, string) { t.Helper() db, err := Open(filepath.Join(t.TempDir(), "test.db")) if err != nil { t.Fatalf("Open: %v", err) } t.Cleanup(func() { db.Close() }) folder, err := CreateMusicFolder(db, "Music", "/music", "music") if err != nil { t.Fatal(err) } mf, err := CreateMediaFile(db, folder.ID, "song.mp3", 100, 0, "h", "mp3", "song", 10, 128, 44100, 2) if err != nil { t.Fatal(err) } song, err := UpsertSong(db, UpsertSongInput{MediaFileID: mf.ID, Title: "Song"}) if err != nil { t.Fatal(err) } return db, song.ID } func TestInsertAndGetPlainLyrics(t *testing.T) { db, songID := openLyricsTestDB(t) lines := []LyricsLineInput{{Text: "Line one"}, {Text: "Line two"}} if _, err := InsertLyrics(db, songID, "embedded", "xxx", false, lines); err != nil { t.Fatalf("InsertLyrics: %v", err) } entries, err := GetSongStructuredLyrics(db, songID) if err != nil { t.Fatalf("GetSongStructuredLyrics: %v", err) } if len(entries) != 1 { t.Fatalf("entries = %v, want 1", entries) } e := entries[0] if e.Synced || e.Source != "embedded" || e.Language != "xxx" { t.Errorf("entry = %+v", e) } if len(e.Lines) != 2 || e.Lines[0].Text != "Line one" { t.Fatalf("lines = %+v", e.Lines) } if e.Lines[0].StartMs != nil { t.Error("plain lyrics line should have nil StartMs") } } func TestInsertAndGetSyncedLyricsWithWordCues(t *testing.T) { db, songID := openLyricsTestDB(t) start0 := 1000 end0 := 1500 lines := []LyricsLineInput{ { StartMs: &start0, Text: "Hello world", Words: []LyricsWordInput{ {StartMs: 1000, EndMs: &end0, ByteStart: 0, ByteEnd: 5, Text: "Hello"}, {StartMs: 1500, ByteStart: 6, ByteEnd: 11, Text: "world"}, }, }, } if _, err := InsertLyrics(db, songID, "external", "en", true, lines); err != nil { t.Fatalf("InsertLyrics: %v", err) } entries, err := GetSongStructuredLyrics(db, songID) if err != nil { t.Fatalf("GetSongStructuredLyrics: %v", err) } if len(entries) != 1 || !entries[0].Synced { t.Fatalf("entries = %+v", entries) } line := entries[0].Lines[0] if line.StartMs == nil || *line.StartMs != 1000 { t.Fatalf("line.StartMs = %v, want 1000", line.StartMs) } if len(line.Words) != 2 { t.Fatalf("words = %+v, want 2", line.Words) } if line.Words[0].EndMs == nil || *line.Words[0].EndMs != 1500 { t.Errorf("Words[0].EndMs = %v, want 1500", line.Words[0].EndMs) } if line.Words[1].EndMs != nil { t.Errorf("Words[1].EndMs = %v, want nil (not provided)", line.Words[1].EndMs) } if line.Words[0].ByteStart != 0 || line.Words[0].ByteEnd != 5 { t.Errorf("Words[0] byte offsets = %d,%d, want 0,5", line.Words[0].ByteStart, line.Words[0].ByteEnd) } } func TestMultipleLyricsEntriesPerSong(t *testing.T) { db, songID := openLyricsTestDB(t) if _, err := InsertLyrics(db, songID, "embedded", "xxx", false, []LyricsLineInput{{Text: "Plain"}}); err != nil { t.Fatal(err) } start := 0 if _, err := InsertLyrics(db, songID, "external", "en", true, []LyricsLineInput{{StartMs: &start, Text: "Synced"}}); err != nil { t.Fatal(err) } entries, err := GetSongStructuredLyrics(db, songID) if err != nil { t.Fatal(err) } if len(entries) != 2 { t.Fatalf("entries = %v, want 2 (multiple lyric sets preserved)", entries) } } func TestClearSongLyricsCascades(t *testing.T) { db, songID := openLyricsTestDB(t) if _, err := InsertLyrics(db, songID, "embedded", "xxx", false, []LyricsLineInput{{Text: "Plain"}}); err != nil { t.Fatal(err) } if err := ClearSongLyrics(db, songID); err != nil { t.Fatalf("ClearSongLyrics: %v", err) } entries, err := GetSongStructuredLyrics(db, songID) if err != nil { t.Fatal(err) } if len(entries) != 0 { t.Fatalf("entries = %v, want 0 after clear", entries) } }