mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
A speaker<->marge recents sync could re-store the same recent (same ID) multiple times — observed live as one STORED_MUSIC track appearing 4x in the speaker's /recents, the service's stored Recents.xml, and /full. The duplicates crowd the capped (10) recents list and evict other sources (e.g. a freshly played Spotify track never appears). SaveConfiguredSources already dedups by ID; SaveRecents did not, so dupes introduced by any path (AddRecent move-to-front, syncRecents from the speaker's /full, setup/health) persisted and fed back through the sync loop. SaveRecents now dedups by ID (first occurrence wins) at the single chokepoint all callers share, so the list self-heals on the next write. Regression test added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
68 lines
1.7 KiB
Go
68 lines
1.7 KiB
Go
package datastore
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/gesellix/bose-soundtouch/pkg/models"
|
|
)
|
|
|
|
// TestSaveRecents_DeduplicatesByID is a regression test for the recents
|
|
// duplication bug: a speaker<->marge sync could re-store the same recent (same
|
|
// ID) multiple times, crowding the capped list and evicting other sources from
|
|
// the speaker's recents. SaveRecents must dedup by ID (first occurrence wins).
|
|
func TestSaveRecents_DeduplicatesByID(t *testing.T) {
|
|
tmp, err := os.MkdirTemp("", "recents-dedup-*")
|
|
if err != nil {
|
|
t.Fatalf("temp dir: %v", err)
|
|
}
|
|
|
|
defer func() { _ = os.RemoveAll(tmp) }()
|
|
|
|
ds := NewDataStore(tmp)
|
|
account, device := "6919733", "A81B6A536A98"
|
|
|
|
mk := func(id, name string) models.ServiceRecent {
|
|
var r models.ServiceRecent
|
|
r.ID = id
|
|
r.Name = name
|
|
r.Source = "7"
|
|
r.SourceAccount = "4d696e69-444c-164e-9d41-72ecda78e4c1/0"
|
|
r.Location = "1$4$2 TRACK"
|
|
|
|
return r
|
|
}
|
|
|
|
// Same ID four times (the observed live state), plus two distinct recents.
|
|
in := []models.ServiceRecent{
|
|
mk("260614006", "03 - Salvation"),
|
|
mk("260614006", "03 - Salvation"),
|
|
mk("260614006", "03 - Salvation"),
|
|
mk("260614006", "03 - Salvation"),
|
|
mk("260614004", "06 - Back Burner"),
|
|
mk("260613001", "Artifact"),
|
|
}
|
|
|
|
if err := ds.SaveRecents(account, device, in); err != nil {
|
|
t.Fatalf("SaveRecents: %v", err)
|
|
}
|
|
|
|
out, err := ds.GetRecents(account, device)
|
|
if err != nil {
|
|
t.Fatalf("GetRecents: %v", err)
|
|
}
|
|
|
|
counts := map[string]int{}
|
|
for _, r := range out {
|
|
counts[r.ID]++
|
|
}
|
|
|
|
if counts["260614006"] != 1 {
|
|
t.Errorf("duplicate recent not deduped: id 260614006 appears %d times (want 1)", counts["260614006"])
|
|
}
|
|
|
|
if len(out) != 3 {
|
|
t.Errorf("expected 3 distinct recents, got %d: %+v", len(out), counts)
|
|
}
|
|
}
|