fix(quick-13): resolve lint issues in main source files
- Fix errcheck for db.Close() in testhelper.go - Fix errcheck, nlreturn, wsl, gofumpt issues in genevents/main.go - Fix gofumpt and wsl issues in library.go
This commit is contained in:
@@ -72,7 +72,10 @@ func TestConfig_LoadSave_Roundtrip(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if loaded.Theme.BackgroundShade != theme.BackgroundLight {
|
if loaded.Theme.BackgroundShade != theme.BackgroundLight {
|
||||||
t.Errorf("Theme.BackgroundShade = %q, want %q", loaded.Theme.BackgroundShade, theme.BackgroundLight)
|
t.Errorf(
|
||||||
|
"Theme.BackgroundShade = %q, want %q",
|
||||||
|
loaded.Theme.BackgroundShade, theme.BackgroundLight,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify tracklist.
|
// Verify tracklist.
|
||||||
@@ -86,13 +89,19 @@ func TestConfig_LoadSave_Roundtrip(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for i, want := range wantColumns {
|
for i, want := range wantColumns {
|
||||||
if loaded.TrackList.Columns[i].ID != want {
|
if loaded.TrackList.Columns[i].ID != want {
|
||||||
t.Errorf("TrackList.Columns[%d].ID = %q, want %q", i, loaded.TrackList.Columns[i].ID, want)
|
t.Errorf(
|
||||||
|
"TrackList.Columns[%d].ID = %q, want %q",
|
||||||
|
i, loaded.TrackList.Columns[i].ID, want,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify favorites.
|
// Verify favorites.
|
||||||
if loaded.Favorites.IconStyle != favorites.IconStar {
|
if loaded.Favorites.IconStyle != favorites.IconStar {
|
||||||
t.Errorf("Favorites.IconStyle = %q, want %q", loaded.Favorites.IconStyle, favorites.IconStar)
|
t.Errorf(
|
||||||
|
"Favorites.IconStyle = %q, want %q",
|
||||||
|
loaded.Favorites.IconStyle, favorites.IconStar,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if loaded.Favorites.PinDefault != false {
|
if loaded.Favorites.PinDefault != false {
|
||||||
@@ -105,7 +114,10 @@ func TestConfig_LoadSave_Roundtrip(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if loaded.Library.ScanConcurrency != library.ScanConcurrencySSD {
|
if loaded.Library.ScanConcurrency != library.ScanConcurrencySSD {
|
||||||
t.Errorf("Library.ScanConcurrency = %q, want %q", loaded.Library.ScanConcurrency, library.ScanConcurrencySSD)
|
t.Errorf(
|
||||||
|
"Library.ScanConcurrency = %q, want %q",
|
||||||
|
loaded.Library.ScanConcurrency, library.ScanConcurrencySSD,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify window.
|
// Verify window.
|
||||||
@@ -206,9 +218,6 @@ func TestConfig_ApplyDefaults_NilSubConfigs(t *testing.T) {
|
|||||||
if c.Favorites == nil {
|
if c.Favorites == nil {
|
||||||
t.Error("Favorites should not be nil after applyDefaults")
|
t.Error("Favorites should not be nil after applyDefaults")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Library is intentionally left nil by applyDefaults when it
|
|
||||||
// starts as nil (no library dir configured yet).
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// containsSubstring is a test helper for checking error messages.
|
// containsSubstring is a test helper for checking error messages.
|
||||||
|
|||||||
@@ -44,28 +44,50 @@ func seedSearchData(t *testing.T, db *DB) {
|
|||||||
intPtr := func(v int64) *int64 { return &v }
|
intPtr := func(v int64) *int64 { return &v }
|
||||||
|
|
||||||
tracks := []track{
|
tracks := []track{
|
||||||
{1, "/music/queen/bohemian_rhapsody.mp3", "Bohemian Rhapsody", "Queen", "A Night at the Opera", intPtr(11), intPtr(1), 1975, "Rock", "Freddie Mercury", 354000, 0, 44100, 16, 2, 320000, 8500000},
|
{
|
||||||
{2, "/music/beyonce/halo.flac", "Halo", "Beyoncé", "Lemonade", intPtr(1), intPtr(1), 2008, "Pop", "Ryan Tedder", 261000, 1, 96000, 24, 2, 1411000, 42000000},
|
1, "/music/queen/bohemian_rhapsody.mp3", "Bohemian Rhapsody", "Queen",
|
||||||
{3, "/music/acdc/back_in_black.mp3", "Back in Black", "AC/DC", "Back in Black", intPtr(1), intPtr(1), 1980, "Hard Rock", "Angus Young", 255000, 0, 44100, 16, 2, 320000, 6100000},
|
"A Night at the Opera", intPtr(11), intPtr(1), 1975, "Rock",
|
||||||
{4, "/music/pinkfloyd/comfortably_numb.flac", "Comfortably Numb", "Pink Floyd", "The Dark Side of the Moon", intPtr(6), intPtr(1), 1979, "Progressive Rock", "David Gilmour", 382000, 1, 96000, 24, 2, 1411000, 54000000},
|
"Freddie Mercury", 354000, 0, 44100, 16, 2, 320000, 8500000,
|
||||||
{5, "/music/queen/another_one_bites_the_dust.mp3", "Another One Bites the Dust", "Queen", "The Game", intPtr(3), intPtr(1), 1980, "Funk Rock", "John Deacon", 215000, 0, 44100, 16, 2, 320000, 5200000},
|
},
|
||||||
{6, "/music/acdc/thunderstruck.mp3", "Thunderstruck", "AC/DC", "The Razors Edge", intPtr(1), intPtr(1), 1990, "Hard Rock", "Angus Young", 292000, 0, 44100, 16, 2, 320000, 7000000},
|
{
|
||||||
{7, "/music/qotsa/queen_of_the_stone_age.mp3", "Queen of the Stone Age", "Queens of the Stone Age", "Rated R", intPtr(1), intPtr(1), 2000, "Stoner Rock", "Josh Homme", 310000, 0, 44100, 16, 2, 320000, 7400000},
|
2, "/music/beyonce/halo.flac", "Halo", "Beyoncé", "Lemonade",
|
||||||
|
intPtr(1), intPtr(1), 2008, "Pop", "Ryan Tedder", 261000, 1,
|
||||||
|
96000, 24, 2, 1411000, 42000000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
3, "/music/acdc/back_in_black.mp3", "Back in Black", "AC/DC",
|
||||||
|
"Back in Black", intPtr(1), intPtr(1), 1980, "Hard Rock",
|
||||||
|
"Angus Young", 255000, 0, 44100, 16, 2, 320000, 6100000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
4, "/music/pinkfloyd/comfortably_numb.flac", "Comfortably Numb",
|
||||||
|
"Pink Floyd", "The Dark Side of the Moon", intPtr(6), intPtr(1),
|
||||||
|
1979, "Progressive Rock", "David Gilmour", 382000, 1, 96000, 24,
|
||||||
|
2, 1411000, 54000000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
5, "/music/queen/another_one_bites_the_dust.mp3",
|
||||||
|
"Another One Bites the Dust", "Queen", "The Game", intPtr(3),
|
||||||
|
intPtr(1), 1980, "Funk Rock", "John Deacon", 215000, 0, 44100,
|
||||||
|
16, 2, 320000, 5200000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
6, "/music/acdc/thunderstruck.mp3", "Thunderstruck", "AC/DC",
|
||||||
|
"The Razors Edge", intPtr(1), intPtr(1), 1990, "Hard Rock",
|
||||||
|
"Angus Young", 292000, 0, 44100, 16, 2, 320000, 7000000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
7, "/music/qotsa/queen_of_the_stone_age.mp3",
|
||||||
|
"Queen of the Stone Age", "Queens of the Stone Age", "Rated R",
|
||||||
|
intPtr(1), intPtr(1), 2000, "Stoner Rock", "Josh Homme", 310000,
|
||||||
|
0, 44100, 16, 2, 320000, 7400000,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build unique sets.
|
// Build unique sets.
|
||||||
type artistEntry struct {
|
|
||||||
id int64
|
|
||||||
text string
|
|
||||||
}
|
|
||||||
|
|
||||||
type albumEntry struct {
|
|
||||||
id int64
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
artistMap := map[string]int64{}
|
artistMap := map[string]int64{}
|
||||||
albumMap := map[string]int64{}
|
albumMap := map[string]int64{}
|
||||||
|
|
||||||
var artistID, albumID int64
|
var artistID, albumID int64
|
||||||
|
|
||||||
for _, tr := range tracks {
|
for _, tr := range tracks {
|
||||||
@@ -104,6 +126,7 @@ func seedSearchData(t *testing.T, db *DB) {
|
|||||||
|
|
||||||
// Insert genres + recording_genres.
|
// Insert genres + recording_genres.
|
||||||
genreMap := map[string]int64{}
|
genreMap := map[string]int64{}
|
||||||
|
|
||||||
var genreID int64
|
var genreID int64
|
||||||
|
|
||||||
for _, tr := range tracks {
|
for _, tr := range tracks {
|
||||||
@@ -131,8 +154,11 @@ func seedSearchData(t *testing.T, db *DB) {
|
|||||||
|
|
||||||
// Insert recording.
|
// Insert recording.
|
||||||
_, err := db.ExecContext(
|
_, err := db.ExecContext(
|
||||||
"INSERT INTO recordings (id, name, artist_credit_id, track_number, disc_number, year, genre, composer) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
"INSERT INTO recordings (id, name, artist_credit_id, "+
|
||||||
tr.id, tr.title, acID, tr.trackNum, tr.discNum, tr.year, tr.genre, tr.composer,
|
"track_number, disc_number, year, genre, composer) "+
|
||||||
|
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
tr.id, tr.title, acID, tr.trackNum, tr.discNum,
|
||||||
|
tr.year, tr.genre, tr.composer,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("insert recording %d %q: %v", tr.id, tr.title, err)
|
t.Fatalf("insert recording %d %q: %v", tr.id, tr.title, err)
|
||||||
@@ -140,8 +166,12 @@ func seedSearchData(t *testing.T, db *DB) {
|
|||||||
|
|
||||||
// Insert audio_files.
|
// Insert audio_files.
|
||||||
_, err = db.ExecContext(
|
_, err = db.ExecContext(
|
||||||
"INSERT INTO audio_files (id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
"INSERT INTO audio_files (id, file_path, "+
|
||||||
tr.id, tr.filePath, tr.lenMs, tr.ftID, tr.id, tr.sr, tr.bd, tr.ch, tr.br, tr.fsize,
|
"length_milliseconds, file_type_id, recording_id, "+
|
||||||
|
"sample_rate, bit_depth, channels, bitrate, file_size) "+
|
||||||
|
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
tr.id, tr.filePath, tr.lenMs, tr.ftID, tr.id,
|
||||||
|
tr.sr, tr.bd, tr.ch, tr.br, tr.fsize,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("insert audio_file %d: %v", tr.id, err)
|
t.Fatalf("insert audio_file %d: %v", tr.id, err)
|
||||||
@@ -149,7 +179,9 @@ func seedSearchData(t *testing.T, db *DB) {
|
|||||||
|
|
||||||
// Link recording to release_group.
|
// Link recording to release_group.
|
||||||
_, err = db.ExecContext(
|
_, err = db.ExecContext(
|
||||||
"INSERT INTO release_group_recordings (release_group_id, recording_id, track_number, disc_number) VALUES (?, ?, ?, ?)",
|
"INSERT INTO release_group_recordings "+
|
||||||
|
"(release_group_id, recording_id, track_number, disc_number) "+
|
||||||
|
"VALUES (?, ?, ?, ?)",
|
||||||
rgID, tr.id, tr.trackNum, tr.discNum,
|
rgID, tr.id, tr.trackNum, tr.discNum,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -368,6 +400,7 @@ func TestSearchFTS_SpecialCharacters(t *testing.T) {
|
|||||||
|
|
||||||
// Verify at least one AC/DC track is present.
|
// Verify at least one AC/DC track is present.
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
if r.Artist == "AC/DC" {
|
if r.Artist == "AC/DC" {
|
||||||
found = true
|
found = true
|
||||||
@@ -432,6 +465,7 @@ func TestSearchFTS_Diacritics(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
if r.Artist == "Beyoncé" {
|
if r.Artist == "Beyoncé" {
|
||||||
found = true
|
found = true
|
||||||
@@ -489,6 +523,7 @@ func TestSearchFTSByFilename(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
|
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
if r.Title == "Bohemian Rhapsody" {
|
if r.Title == "Bohemian Rhapsody" {
|
||||||
found = true
|
found = true
|
||||||
@@ -620,7 +655,9 @@ func TestInsertAndDeleteSearchIndex(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Insert into search index.
|
// Insert into search index.
|
||||||
if err := db.InsertSearchIndex(1, "/test/track.mp3", "Test Track", "Test Artist", "Test Album"); err != nil {
|
if err := db.InsertSearchIndex(
|
||||||
|
1, "/test/track.mp3", "Test Track", "Test Artist", "Test Album",
|
||||||
|
); err != nil {
|
||||||
t.Fatalf("InsertSearchIndex: %v", err)
|
t.Fatalf("InsertSearchIndex: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -781,11 +818,13 @@ func TestMigrationsApplied(t *testing.T) {
|
|||||||
|
|
||||||
if !rows.Next() {
|
if !rows.Next() {
|
||||||
_ = rows.Close()
|
_ = rows.Close()
|
||||||
|
|
||||||
t.Fatal("PRAGMA user_version: no row returned")
|
t.Fatal("PRAGMA user_version: no row returned")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rows.Scan(&version); err != nil {
|
if err := rows.Scan(&version); err != nil {
|
||||||
_ = rows.Close()
|
_ = rows.Close()
|
||||||
|
|
||||||
t.Fatalf("scan user_version: %v", err)
|
t.Fatalf("scan user_version: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ func NewTestDB(t *testing.T) *DB {
|
|||||||
|
|
||||||
queries := sqlcgen.New(db)
|
queries := sqlcgen.New(db)
|
||||||
|
|
||||||
t.Cleanup(func() { db.Close() })
|
t.Cleanup(func() { _ = db.Close() })
|
||||||
|
|
||||||
return &DB{
|
return &DB{
|
||||||
db: db,
|
db: db,
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ func main() {
|
|||||||
|
|
||||||
if *output == "" || *output == "/dev/stdout" {
|
if *output == "" || *output == "/dev/stdout" {
|
||||||
fmt.Print(ts)
|
fmt.Print(ts)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +58,7 @@ type constEntry struct {
|
|||||||
// groups in declaration order.
|
// groups in declaration order.
|
||||||
func parseEvents(path string) ([]constGroup, error) {
|
func parseEvents(path string) ([]constGroup, error) {
|
||||||
fset := token.NewFileSet()
|
fset := token.NewFileSet()
|
||||||
|
|
||||||
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
|
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parse %s: %w", path, err)
|
return nil, fmt.Errorf("parse %s: %w", path, err)
|
||||||
@@ -82,11 +84,14 @@ func parseEvents(path string) ([]constGroup, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, name := range vs.Names {
|
for i, name := range vs.Names {
|
||||||
if i >= len(vs.Values) {
|
if i >= len(vs.Values) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
bl, ok := vs.Values[i].(*ast.BasicLit)
|
bl, ok := vs.Values[i].(*ast.BasicLit)
|
||||||
|
|
||||||
if !ok || bl.Kind != token.STRING {
|
if !ok || bl.Kind != token.STRING {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -110,6 +115,7 @@ func parseEvents(path string) ([]constGroup, error) {
|
|||||||
func cleanComment(s string) string {
|
func cleanComment(s string) string {
|
||||||
s = strings.TrimSpace(s)
|
s = strings.TrimSpace(s)
|
||||||
s = strings.TrimSuffix(s, ".")
|
s = strings.TrimSuffix(s, ".")
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +132,7 @@ func generateTypeScript(groups []constGroup) string {
|
|||||||
if g.Comment != "" {
|
if g.Comment != "" {
|
||||||
b.WriteString(" // " + g.Comment + "\n")
|
b.WriteString(" // " + g.Comment + "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range g.Consts {
|
for _, c := range g.Consts {
|
||||||
b.WriteString(fmt.Sprintf(" %s: %q,\n", c.Name, c.Value))
|
b.WriteString(fmt.Sprintf(" %s: %q,\n", c.Name, c.Value))
|
||||||
}
|
}
|
||||||
@@ -146,20 +153,26 @@ func generateTypeScript(groups []constGroup) string {
|
|||||||
// then renames it into place for atomic replacement.
|
// then renames it into place for atomic replacement.
|
||||||
func writeAtomic(path, data string) error {
|
func writeAtomic(path, data string) error {
|
||||||
dir := filepath.Dir(path)
|
dir := filepath.Dir(path)
|
||||||
|
|
||||||
tmp, err := os.CreateTemp(dir, ".genevents-*.tmp")
|
tmp, err := os.CreateTemp(dir, ".genevents-*.tmp")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpName := tmp.Name()
|
tmpName := tmp.Name()
|
||||||
|
|
||||||
if _, err := tmp.WriteString(data); err != nil {
|
if _, err := tmp.WriteString(data); err != nil {
|
||||||
tmp.Close()
|
_ = tmp.Close()
|
||||||
os.Remove(tmpName)
|
_ = os.Remove(tmpName)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tmp.Close(); err != nil {
|
if err := tmp.Close(); err != nil {
|
||||||
os.Remove(tmpName)
|
_ = os.Remove(tmpName)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.Rename(tmpName, path)
|
return os.Rename(tmpName, path)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ func TestFavoritesConfig_Validate_InvalidIconStyle(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := &Config{IconStyle: "diamond"}
|
c := &Config{IconStyle: "diamond"}
|
||||||
|
|
||||||
err := c.Validate()
|
err := c.Validate()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Validate() expected error for unknown icon style, got nil")
|
t.Fatal("Validate() expected error for unknown icon style, got nil")
|
||||||
|
|||||||
@@ -229,6 +229,7 @@ func (l *Library) Scan() (*ScanMetrics, error) {
|
|||||||
"count", len(existingFiles),
|
"count", len(existingFiles),
|
||||||
"library-directory", l.conf.DirectoryPath,
|
"library-directory", l.conf.DirectoryPath,
|
||||||
)
|
)
|
||||||
|
|
||||||
workChan := make(chan scanWork, 100)
|
workChan := make(chan scanWork, 100)
|
||||||
resultChan := make(chan importResult, 100)
|
resultChan := make(chan importResult, 100)
|
||||||
|
|
||||||
@@ -531,10 +532,14 @@ func (l *Library) Scan() (*ScanMetrics, error) {
|
|||||||
// point so it is safe to close.
|
// point so it is safe to close.
|
||||||
thumbStart := time.Now()
|
thumbStart := time.Now()
|
||||||
|
|
||||||
runtime.EventsEmit(l.ctx, events.LibraryScanProgress,
|
runtime.EventsEmit(l.ctx, events.LibraryScanProgress, ScanProgress{
|
||||||
ScanProgress{Phase: "thumbnails", Total: totalFiles,
|
Phase: "thumbnails",
|
||||||
Processed: a + s + u, Added: a, Skipped: s, Updated: u},
|
Total: totalFiles,
|
||||||
)
|
Processed: a + s + u,
|
||||||
|
Added: a,
|
||||||
|
Skipped: s,
|
||||||
|
Updated: u,
|
||||||
|
})
|
||||||
|
|
||||||
close(thumbChan)
|
close(thumbChan)
|
||||||
thumbWg.Wait()
|
thumbWg.Wait()
|
||||||
@@ -542,10 +547,10 @@ func (l *Library) Scan() (*ScanMetrics, error) {
|
|||||||
metrics.ThumbnailWallClock = time.Since(thumbStart)
|
metrics.ThumbnailWallClock = time.Since(thumbStart)
|
||||||
|
|
||||||
// --- Phase 5: orphan cleanup ---
|
// --- Phase 5: orphan cleanup ---
|
||||||
runtime.EventsEmit(l.ctx, events.LibraryScanProgress,
|
runtime.EventsEmit(l.ctx, events.LibraryScanProgress, ScanProgress{
|
||||||
ScanProgress{Phase: "orphans", Total: totalFiles,
|
Phase: "orphans", Total: totalFiles,
|
||||||
Processed: a + s + u, Added: a, Skipped: s, Updated: u},
|
Processed: a + s + u, Added: a, Skipped: s, Updated: u,
|
||||||
)
|
})
|
||||||
|
|
||||||
orphanStart := time.Now()
|
orphanStart := time.Now()
|
||||||
|
|
||||||
|
|||||||
@@ -396,7 +396,10 @@ func TestCachedLinkArtist(t *testing.T) {
|
|||||||
lib.cachedLinkArtist(q, cache, metrics, "Queen", ac.ID)
|
lib.cachedLinkArtist(q, cache, metrics, "Queen", ac.ID)
|
||||||
|
|
||||||
if len(cache.linkedCredits) != 1 {
|
if len(cache.linkedCredits) != 1 {
|
||||||
t.Errorf("linkedCredits after duplicate = %d, want 1 (should skip)", len(cache.linkedCredits))
|
t.Errorf(
|
||||||
|
"linkedCredits after duplicate = %d, want 1 (should skip)",
|
||||||
|
len(cache.linkedCredits),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,6 +539,7 @@ func TestResolveReleaseGroup(t *testing.T) {
|
|||||||
// Cache key is composite: "albumName\x00artistCreditID".
|
// Cache key is composite: "albumName\x00artistCreditID".
|
||||||
cacheKey := fmt.Sprintf("%s\x00%d", "A Night at the Opera", ac.ID)
|
cacheKey := fmt.Sprintf("%s\x00%d", "A Night at the Opera", ac.ID)
|
||||||
cachedRG := cache.releaseGroups[cacheKey]
|
cachedRG := cache.releaseGroups[cacheKey]
|
||||||
|
|
||||||
if !cachedRG.CoverArtID.Valid {
|
if !cachedRG.CoverArtID.Valid {
|
||||||
t.Error("expected CoverArtID to be set after update")
|
t.Error("expected CoverArtID to be set after update")
|
||||||
}
|
}
|
||||||
@@ -616,7 +620,9 @@ func TestOrphanDeletion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add FTS search index entry.
|
// Add FTS search index entry.
|
||||||
if err := db.InsertSearchIndex(af.ID, "/music/test.mp3", "Test Song", "Test Artist", ""); err != nil {
|
if err := db.InsertSearchIndex(
|
||||||
|
af.ID, "/music/test.mp3", "Test Song", "Test Artist", "",
|
||||||
|
); err != nil {
|
||||||
t.Fatalf("insert search index: %v", err)
|
t.Fatalf("insert search index: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -651,11 +657,12 @@ func TestOrphanDeletion(t *testing.T) {
|
|||||||
// become stale but harmless (they reference a non-existent
|
// become stale but harmless (they reference a non-existent
|
||||||
// audio_file ID, so JOINs return no results).
|
// audio_file ID, so JOINs return no results).
|
||||||
// ClearSearchIndex (used during full rescan) handles bulk cleanup.
|
// ClearSearchIndex (used during full rescan) handles bulk cleanup.
|
||||||
|
// DeleteSearchIndex on contentless FTS5 is expected to error.
|
||||||
|
// Not a fatal error — documents the contentless FTS5 limitation.
|
||||||
err = db.DeleteSearchIndex(af.ID)
|
err = db.DeleteSearchIndex(af.ID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Log("DeleteSearchIndex succeeded (unexpected for contentless FTS5)")
|
t.Log("DeleteSearchIndex succeeded (unexpected for contentless FTS5)")
|
||||||
}
|
}
|
||||||
// Not a fatal error — documents the contentless FTS5 limitation.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -705,7 +712,10 @@ func TestEntityCache_EmptyFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if albumACID.Int64 != trackAC.ID {
|
if albumACID.Int64 != trackAC.ID {
|
||||||
t.Errorf("empty AlbumArtist should reuse track credit: got %d, want %d", albumACID.Int64, trackAC.ID)
|
t.Errorf(
|
||||||
|
"empty AlbumArtist should reuse track credit: got %d, want %d",
|
||||||
|
albumACID.Int64, trackAC.ID,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveAlbumArtistCredit when AlbumArtist matches Artist also reuses.
|
// resolveAlbumArtistCredit when AlbumArtist matches Artist also reuses.
|
||||||
@@ -716,6 +726,9 @@ func TestEntityCache_EmptyFields(t *testing.T) {
|
|||||||
|
|
||||||
sameACID := lib.resolveAlbumArtistCredit(q, cache, metrics, sameTags, trackAC.ID)
|
sameACID := lib.resolveAlbumArtistCredit(q, cache, metrics, sameTags, trackAC.ID)
|
||||||
if sameACID.Int64 != trackAC.ID {
|
if sameACID.Int64 != trackAC.ID {
|
||||||
t.Errorf("matching AlbumArtist should reuse track credit: got %d, want %d", sameACID.Int64, trackAC.ID)
|
t.Errorf(
|
||||||
|
"matching AlbumArtist should reuse track credit: got %d, want %d",
|
||||||
|
sameACID.Int64, trackAC.ID,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,8 +126,11 @@ func TestUserVolume_ToVolume_Roundtrip(t *testing.T) {
|
|||||||
|
|
||||||
diff := int(roundtripped) - int(i)
|
diff := int(roundtripped) - int(i)
|
||||||
if diff < -1 || diff > 1 {
|
if diff < -1 || diff > 1 {
|
||||||
t.Errorf("Roundtrip UserVolume(%d) -> Volume(%f) -> UserVolume(%d): drift %d exceeds ±1",
|
t.Errorf(
|
||||||
i, vol, roundtripped, diff)
|
"Roundtrip UserVolume(%d) -> Volume(%f) -> UserVolume(%d): "+
|
||||||
|
"drift %d exceeds ±1",
|
||||||
|
i, vol, roundtripped, diff,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,8 +141,11 @@ func TestUserVolume_ToVolume_Roundtrip(t *testing.T) {
|
|||||||
roundtripped := vol.ToUserVolume()
|
roundtripped := vol.ToUserVolume()
|
||||||
|
|
||||||
if roundtripped != uv {
|
if roundtripped != uv {
|
||||||
t.Errorf("Exact roundtrip UserVolume(%d) -> Volume(%f) -> UserVolume(%d): want exact match",
|
t.Errorf(
|
||||||
uv, vol, roundtripped)
|
"Exact roundtrip UserVolume(%d) -> Volume(%f) -> "+
|
||||||
|
"UserVolume(%d): want exact match",
|
||||||
|
uv, vol, roundtripped,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ func newTestQueueDirect(tracks int, currentIndex int) *Queue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
q.tracks = make([]Track, tracks)
|
q.tracks = make([]Track, tracks)
|
||||||
for i := 0; i < tracks; i++ {
|
for i := range tracks {
|
||||||
q.tracks[i] = Track{FilePath: "/test/track.mp3", Position: int64(i)}
|
q.tracks[i] = Track{FilePath: "/test/track.mp3", Position: int64(i)}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,11 +135,15 @@ func TestGenerateShuffleOrder_Properties(t *testing.T) {
|
|||||||
|
|
||||||
// Property 2: current track is at shuffleOrder[0].
|
// Property 2: current track is at shuffleOrder[0].
|
||||||
if q.shuffleOrder[0] != tc.currentIdx {
|
if q.shuffleOrder[0] != tc.currentIdx {
|
||||||
t.Errorf("shuffleOrder[0]: got %d, want %d (currentIndex)", q.shuffleOrder[0], tc.currentIdx)
|
t.Errorf(
|
||||||
|
"shuffleOrder[0]: got %d, want %d (currentIndex)",
|
||||||
|
q.shuffleOrder[0], tc.currentIdx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Property 3: all indices present (no duplicates, no missing).
|
// Property 3: all indices present (no duplicates, no missing).
|
||||||
seen := make(map[int]bool, tc.trackCount)
|
seen := make(map[int]bool, tc.trackCount)
|
||||||
|
|
||||||
for _, idx := range q.shuffleOrder {
|
for _, idx := range q.shuffleOrder {
|
||||||
if idx < 0 || idx >= tc.trackCount {
|
if idx < 0 || idx >= tc.trackCount {
|
||||||
t.Errorf("shuffleOrder contains out-of-range index: %d", idx)
|
t.Errorf("shuffleOrder contains out-of-range index: %d", idx)
|
||||||
@@ -153,7 +157,10 @@ func TestGenerateShuffleOrder_Properties(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(seen) != tc.trackCount {
|
if len(seen) != tc.trackCount {
|
||||||
t.Errorf("unique indices in shuffleOrder: got %d, want %d", len(seen), tc.trackCount)
|
t.Errorf(
|
||||||
|
"unique indices in shuffleOrder: got %d, want %d",
|
||||||
|
len(seen), tc.trackCount,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -177,6 +184,7 @@ func TestNextIndex_ShuffleMode(t *testing.T) {
|
|||||||
// Advance to index 4 and get next.
|
// Advance to index 4 and get next.
|
||||||
q.currentIndex = 4
|
q.currentIndex = 4
|
||||||
got = q.nextIndex()
|
got = q.nextIndex()
|
||||||
|
|
||||||
if got != 0 {
|
if got != 0 {
|
||||||
t.Errorf("nextIndex (shuffle, pos 2): got %d, want 0", got)
|
t.Errorf("nextIndex (shuffle, pos 2): got %d, want 0", got)
|
||||||
}
|
}
|
||||||
@@ -184,6 +192,7 @@ func TestNextIndex_ShuffleMode(t *testing.T) {
|
|||||||
// At the end of shuffle order with RepeatOff.
|
// At the end of shuffle order with RepeatOff.
|
||||||
q.currentIndex = 1 // last in shuffleOrder
|
q.currentIndex = 1 // last in shuffleOrder
|
||||||
got = q.nextIndex()
|
got = q.nextIndex()
|
||||||
|
|
||||||
if got != -1 {
|
if got != -1 {
|
||||||
t.Errorf("nextIndex (shuffle, end, repeatOff): got %d, want -1", got)
|
t.Errorf("nextIndex (shuffle, end, repeatOff): got %d, want -1", got)
|
||||||
}
|
}
|
||||||
@@ -191,7 +200,11 @@ func TestNextIndex_ShuffleMode(t *testing.T) {
|
|||||||
// At the end of shuffle order with RepeatAll.
|
// At the end of shuffle order with RepeatAll.
|
||||||
q.repeatMode = RepeatAll
|
q.repeatMode = RepeatAll
|
||||||
got = q.nextIndex()
|
got = q.nextIndex()
|
||||||
|
|
||||||
if got != 2 {
|
if got != 2 {
|
||||||
t.Errorf("nextIndex (shuffle, end, repeatAll): got %d, want 2 (wraps to shuffleOrder[0])", got)
|
t.Errorf(
|
||||||
|
"nextIndex (shuffle, end, repeatAll): got %d, want 2 "+
|
||||||
|
"(wraps to shuffleOrder[0])", got,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,10 @@ func TestSaveState_RestoreState_Roundtrip(t *testing.T) {
|
|||||||
// Each track's FilePath, Title, Artist.
|
// Each track's FilePath, Title, Artist.
|
||||||
for i := range s1.Tracks {
|
for i := range s1.Tracks {
|
||||||
if s2.Tracks[i].FilePath != s1.Tracks[i].FilePath {
|
if s2.Tracks[i].FilePath != s1.Tracks[i].FilePath {
|
||||||
t.Errorf("track[%d] FilePath: got %q, want %q", i, s2.Tracks[i].FilePath, s1.Tracks[i].FilePath)
|
t.Errorf(
|
||||||
|
"track[%d] FilePath: got %q, want %q",
|
||||||
|
i, s2.Tracks[i].FilePath, s1.Tracks[i].FilePath,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s2.Tracks[i].Title != s1.Tracks[i].Title {
|
if s2.Tracks[i].Title != s1.Tracks[i].Title {
|
||||||
@@ -43,7 +46,10 @@ func TestSaveState_RestoreState_Roundtrip(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s2.Tracks[i].Artist != s1.Tracks[i].Artist {
|
if s2.Tracks[i].Artist != s1.Tracks[i].Artist {
|
||||||
t.Errorf("track[%d] Artist: got %q, want %q", i, s2.Tracks[i].Artist, s1.Tracks[i].Artist)
|
t.Errorf(
|
||||||
|
"track[%d] Artist: got %q, want %q",
|
||||||
|
i, s2.Tracks[i].Artist, s1.Tracks[i].Artist,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +77,10 @@ func TestSaveState_RestoreState_Roundtrip(t *testing.T) {
|
|||||||
} else {
|
} else {
|
||||||
for i := range q.shuffleOrder {
|
for i := range q.shuffleOrder {
|
||||||
if q2.shuffleOrder[i] != q.shuffleOrder[i] {
|
if q2.shuffleOrder[i] != q.shuffleOrder[i] {
|
||||||
t.Errorf("shuffleOrder[%d]: got %d, want %d", i, q2.shuffleOrder[i], q.shuffleOrder[i])
|
t.Errorf(
|
||||||
|
"shuffleOrder[%d]: got %d, want %d",
|
||||||
|
i, q2.shuffleOrder[i], q.shuffleOrder[i],
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type mockTrackLoader struct {
|
|||||||
|
|
||||||
func (m *mockTrackLoader) LoadFile(filePath string) error {
|
func (m *mockTrackLoader) LoadFile(filePath string) error {
|
||||||
m.loadedFile = filePath
|
m.loadedFile = filePath
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ func seedAudioFiles(t *testing.T, db *database.DB, count int) []string {
|
|||||||
|
|
||||||
paths := make([]string, count)
|
paths := make([]string, count)
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
for i := range count {
|
||||||
recID := i + 1
|
recID := i + 1
|
||||||
afID := i + 1
|
afID := i + 1
|
||||||
fp := fmt.Sprintf("/test/track%d.mp3", i+1)
|
fp := fmt.Sprintf("/test/track%d.mp3", i+1)
|
||||||
@@ -68,7 +69,9 @@ func seedAudioFiles(t *testing.T, db *database.DB, count int) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.ExecContext(
|
_, err = db.ExecContext(
|
||||||
"INSERT OR IGNORE INTO audio_files (id, file_path, length_milliseconds, file_type_id, recording_id) VALUES (?, ?, 180000, 0, ?)",
|
"INSERT OR IGNORE INTO audio_files (id, file_path, "+
|
||||||
|
"length_milliseconds, file_type_id, recording_id) "+
|
||||||
|
"VALUES (?, ?, 180000, 0, ?)",
|
||||||
afID, fp, recID,
|
afID, fp, recID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -292,7 +295,10 @@ func TestRemoveTrack_RemoveCurrentTrack(t *testing.T) {
|
|||||||
|
|
||||||
// After removing currentIndex=2, index should be clamped to valid range.
|
// After removing currentIndex=2, index should be clamped to valid range.
|
||||||
if state.CurrentIndex < 0 || state.CurrentIndex >= len(state.Tracks) {
|
if state.CurrentIndex < 0 || state.CurrentIndex >= len(state.Tracks) {
|
||||||
t.Errorf("currentIndex out of range: got %d, track count %d", state.CurrentIndex, len(state.Tracks))
|
t.Errorf(
|
||||||
|
"currentIndex out of range: got %d, track count %d",
|
||||||
|
state.CurrentIndex, len(state.Tracks),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ func TestThemeConfig_Validate_InvalidHexColor(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := &Config{AccentColor: tt.color, BackgroundShade: BackgroundDark}
|
c := &Config{AccentColor: tt.color, BackgroundShade: BackgroundDark}
|
||||||
|
|
||||||
err := c.Validate()
|
err := c.Validate()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Validate() expected error for invalid hex color, got nil")
|
t.Error("Validate() expected error for invalid hex color, got nil")
|
||||||
@@ -61,6 +62,7 @@ func TestThemeConfig_Validate_InvalidBackgroundShade(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := &Config{AccentColor: "#ffd43b", BackgroundShade: "neon"}
|
c := &Config{AccentColor: "#ffd43b", BackgroundShade: "neon"}
|
||||||
|
|
||||||
err := c.Validate()
|
err := c.Validate()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Validate() expected error for unknown shade, got nil")
|
t.Fatal("Validate() expected error for unknown shade, got nil")
|
||||||
|
|||||||
Reference in New Issue
Block a user