mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 00:26:29 +00:00
fix(datastore): stop INTERNET_RADIO from being re-added on service restart
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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
6c4c420b29
commit
aafc5ba3f9
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
"<sources>",
|
||||
"<source id=\"10004\" type=\"Audio\"",
|
||||
"<source id=\"10003\" type=\"Audio\"",
|
||||
"<source id=\"10002\" type=\"Audio\"",
|
||||
}
|
||||
|
||||
for _, snippet := range expectedSnippets {
|
||||
@@ -660,6 +662,10 @@ func TestMargeAccountSourcesNoDevices(t *testing.T) {
|
||||
t.Errorf("Response must not include AUX (id=10001); body:\n%s", bodyStr)
|
||||
}
|
||||
|
||||
if strings.Contains(bodyStr, "<source id=\"10002\"") {
|
||||
t.Errorf("Response must not include INTERNET_RADIO (id=10002) for accounts with no device; body:\n%s", bodyStr)
|
||||
}
|
||||
|
||||
// Verify that no sources have empty display names
|
||||
if strings.Count(bodyStr, "displayName=\"\"") != 0 {
|
||||
t.Errorf("Expected no sources with empty displayName, got %d: %s", strings.Count(bodyStr, "displayName=\"\""), bodyStr)
|
||||
|
||||
@@ -1219,10 +1219,10 @@ func getAccountSources(ds *datastore.DataStore, account, lastDeviceID string) []
|
||||
if lastDeviceID != "" {
|
||||
sources, err = ds.GetConfiguredSources(account, lastDeviceID)
|
||||
if err == nil {
|
||||
sources = mergeDefaultSources(sources, ds.GetDefaultSources())
|
||||
sources = mergeDefaultSources(sources, ds.GetInitialSources())
|
||||
}
|
||||
} else {
|
||||
sources = ds.GetDefaultSources()
|
||||
sources = ds.GetInitialSources()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -25,7 +25,10 @@ Authorization: Bearer dummy-token
|
||||
// the speaker enumerates AUX from its own hardware via isLocal=true
|
||||
// in :8090/sources. See pkg/service/marge/marge.go getAccountSources
|
||||
// and commit 2b40481 (#195/#269).
|
||||
const expectedIds = ["10002", "10003", "10004"];
|
||||
// INTERNET_RADIO (id=10002) is also excluded — it is a legacy stub
|
||||
// that AfterTouch no longer adds to new or existing devices. See
|
||||
// GetInitialSources() in pkg/service/datastore/datastore.go.
|
||||
const expectedIds = ["10003", "10004", "10005"];
|
||||
for (let i = 0; i < sourceList.length; i++) {
|
||||
const source = sourceList.item(i);
|
||||
const sourceId = source.getAttribute("id");
|
||||
|
||||
@@ -52,7 +52,10 @@ Authorization: Bearer {{token}}
|
||||
// AUX from its own hardware via isLocal=true in :8090/sources. See
|
||||
// pkg/service/marge/marge.go getAccountSources for the reasoning and
|
||||
// commit 2b40481 for the fix that closed #195/#269.
|
||||
client.assert(sourceCount === 5, "Expected 5 cloud sources (INTERNET_RADIO, LOCAL_INTERNET_RADIO, TUNEIN, RADIO_BROWSER, Spotify; AUX is hardware-local) but got " + sourceCount);
|
||||
// INTERNET_RADIO (id=10002) is also excluded — it is a legacy stub
|
||||
// that AfterTouch no longer adds to new or existing devices. See
|
||||
// GetInitialSources() in pkg/service/datastore/datastore.go.
|
||||
client.assert(sourceCount === 4, "Expected 4 cloud sources (LOCAL_INTERNET_RADIO, TUNEIN, RADIO_BROWSER, Spotify; AUX and INTERNET_RADIO are excluded) but got " + sourceCount);
|
||||
|
||||
// Explicit negative assertion: AUX must not appear in /full.
|
||||
var sourceList = sources.getElementsByTagName("source");
|
||||
|
||||
Reference in New Issue
Block a user