From aafc5ba3f9b5ec360fe31e9a5c09e883971ae840 Mon Sep 17 00:00:00 2001 From: Tobias Gesellchen Date: Tue, 26 May 2026 22:39:46 +0200 Subject: [PATCH] fix(datastore): stop INTERNET_RADIO from being re-added on service restart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit initializeDefaultSources() called GetDefaultSources(), which includes the legacy INTERNET_RADIO stub (ID 10002). On every service start it would re-add that entry to any device whose Sources.xml had it removed — including devices where the stale_internet_radio health-check quick fix was applied — silently undoing the clean-up. getAccountSources() in marge.go had the same issue: it passed the full default list into the /full cloud response, causing a phantom "sources_xml_diff" Info finding after a clean-up. Fix: export the existing private getInitialSources() as GetInitialSources() (excludes INTERNET_RADIO) and use it in both call sites instead of GetDefaultSources(). Existing devices that still have INTERNET_RADIO in their Sources.xml are unaffected: the merge loop only appends entries that are missing, so a present entry is preserved (the token is refreshed as before). Update unit and integration test expectations accordingly: the no-device fallback now returns 3 cloud sources (LOCAL_INTERNET_RADIO, TUNEIN, RADIO_BROWSER) instead of 4 (dropping INTERNET_RADIO / ID 10002). Co-Authored-By: Claude Sonnet 4.6 --- cmd/soundtouch-service/main.go | 2 +- pkg/service/datastore/datastore.go | 9 ++++++ pkg/service/handlers/handlers_marge_test.go | 28 +++++++++++-------- pkg/service/marge/marge.go | 4 +-- .../http-client/get_account_sources.http | 5 +++- .../http-client/get_full_account.http | 5 +++- 6 files changed, 37 insertions(+), 16 deletions(-) diff --git a/cmd/soundtouch-service/main.go b/cmd/soundtouch-service/main.go index 62d5df7..5b69b33 100644 --- a/cmd/soundtouch-service/main.go +++ b/cmd/soundtouch-service/main.go @@ -77,7 +77,7 @@ func initializeDefaultSources(ds *datastore.DataStore) { // claimed tracks which stored sources have already been matched by a default, // so two defaults with the same SourceKeyType but different SourceProviderIDs // (e.g. INTERNET_RADIO/2 and INTERNET_RADIO/39) are treated as distinct entries. - defaults := ds.GetDefaultSources() + defaults := ds.GetInitialSources() modified := false claimed := make(map[int]bool) diff --git a/pkg/service/datastore/datastore.go b/pkg/service/datastore/datastore.go index 5ad30f9..2769728 100644 --- a/pkg/service/datastore/datastore.go +++ b/pkg/service/datastore/datastore.go @@ -2173,6 +2173,15 @@ func (ds *DataStore) GetDefaultSources() []models.ConfiguredSource { return ds.getDefaultSources() } +// GetInitialSources returns the default sources for a brand-new device: +// the full default list minus the legacy INTERNET_RADIO stub. +// Use this (not GetDefaultSources) whenever deciding which sources to add +// to a device — re-adding INTERNET_RADIO to existing devices would silently +// undo the stale_internet_radio health-check quick-fix. +func (ds *DataStore) GetInitialSources() []models.ConfiguredSource { + return ds.getInitialSources() +} + // CanonicalSourceByID returns the canonical default ConfiguredSource for one // of the well-known built-in source IDs (10001..10005). The returned source // has its SourceKey.Type / SourceKey.Account mirrored from diff --git a/pkg/service/handlers/handlers_marge_test.go b/pkg/service/handlers/handlers_marge_test.go index 5e4104a..c126d21 100644 --- a/pkg/service/handlers/handlers_marge_test.go +++ b/pkg/service/handlers/handlers_marge_test.go @@ -64,16 +64,17 @@ func TestMargeCreateAccount(t *testing.T) { t.Errorf("Expected 7-digit ID, got %v", resp.ID) } - // Verify default sources. AUX (id=10001, sourceproviderid=9) is - // intentionally excluded from cloud responses — real Bose never - // emitted AUX in /full; the speaker enumerates AUX from its own - // hardware via isLocal=true in :8090/sources. See + // Verify default sources. AUX (id=10001) is intentionally excluded from + // cloud responses — real Bose never emitted AUX in /full; the speaker + // enumerates AUX from its own hardware via isLocal=true in :8090/sources. + // INTERNET_RADIO (id=10002) is also excluded — it is a legacy stub that + // AfterTouch no longer adds to new devices. See GetInitialSources() and // pkg/service/marge/marge.go getAccountSources. - if len(resp.Sources) != 4 { - t.Errorf("Expected 4 cloud default sources (AUX excluded), got %d", len(resp.Sources)) + if len(resp.Sources) != 3 { + t.Errorf("Expected 3 cloud default sources (AUX and INTERNET_RADIO excluded), got %d", len(resp.Sources)) } else { - if resp.Sources[0].ID != "10002" { - t.Errorf("Expected first cloud source ID 10002 (INTERNET_RADIO), got %s", resp.Sources[0].ID) + if resp.Sources[0].ID != "10003" { + t.Errorf("Expected first cloud source ID 10003 (LOCAL_INTERNET_RADIO), got %s", resp.Sources[0].ID) } } @@ -641,13 +642,14 @@ func TestMargeAccountSourcesNoDevices(t *testing.T) { // Verify that we get the default cloud sources with correct IDs. AUX // (id=10001) is intentionally excluded — real Bose never emitted AUX // in cloud responses; the speaker enumerates AUX from its own - // hardware (isLocal=true on :8090/sources). See - // pkg/service/marge/marge.go getAccountSources. + // hardware (isLocal=true on :8090/sources). INTERNET_RADIO (id=10002) + // is also intentionally excluded — it is a legacy stub that AfterTouch + // no longer adds to new or no-device accounts. See GetInitialSources() + // and pkg/service/marge/marge.go getAccountSources. expectedSnippets := []string{ "", "