fix(setup): test override file existence before treating cat output as config (#215)

client.Run uses CombinedOutput, so when
`/mnt/nv/OverrideSdkPrivateCfg.xml` is absent (the default for devices
migrated with pre-0.71.0 code) the cat stderr is returned as the
override config and surfaced to the migration page UI as "Current Config
(on Speaker)". Gate the branch on `[ -f ... ]` first, mirroring the
legacy .original check.

Relates to #209
Relates to #214

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tobias Gesellchen
2026-05-06 12:06:31 +02:00
committed by GitHub
co-authored by Claude Opus 4.7
parent d14f4691a6
commit bcffbc7719
2 changed files with 57 additions and 7 deletions
+11 -7
View File
@@ -508,14 +508,18 @@ func (m *Manager) checkCurrentConfig(summary *MigrationSummary, deviceIP string)
client := m.NewSSH(deviceIP)
// Check for override config (new-style XML migration) — the device prefers this over the original.
if overrideCfg, _ := client.Run(fmt.Sprintf("cat %s", SoundTouchSdkPrivateCfgOverridePath)); overrideCfg != "" {
summary.SSHSuccess = true
// The untouched factory config at the original path is the OriginalConfig.
if origCfg, _ := client.Run(fmt.Sprintf("cat %s", SoundTouchSdkPrivateCfgPath)); origCfg != "" {
summary.OriginalConfig = origCfg
}
// Test existence first: client.Run uses CombinedOutput, so a missing file's stderr would otherwise
// be returned as a non-empty config and surfaced to the UI.
if _, checkErr := client.Run(fmt.Sprintf("[ -f %s ]", SoundTouchSdkPrivateCfgOverridePath)); checkErr == nil {
if overrideCfg, _ := client.Run(fmt.Sprintf("cat %s", SoundTouchSdkPrivateCfgOverridePath)); overrideCfg != "" {
summary.SSHSuccess = true
// The untouched factory config at the original path is the OriginalConfig.
if origCfg, _ := client.Run(fmt.Sprintf("cat %s", SoundTouchSdkPrivateCfgPath)); origCfg != "" {
summary.OriginalConfig = origCfg
}
return overrideCfg, nil
return overrideCfg, nil
}
}
path := SoundTouchSdkPrivateCfgPath
+46
View File
@@ -1535,6 +1535,52 @@ func TestCheckIsMigrated(t *testing.T) {
})
}
// TestCheckCurrentConfig_OverrideMissing reproduces issue #214: when the override
// file does not exist, client.Run returns the cat stderr ("cat: can't open ...")
// via CombinedOutput. The previous implementation treated that non-empty stderr
// as a valid config and surfaced it to the UI. Existence must be tested first.
func TestCheckCurrentConfig_OverrideMissing(t *testing.T) {
m := NewManager("http://aftertouch:8000", nil, nil)
originalCfg := "<SoundTouchSdkPrivateCfg><margeServerUrl>http://streaming.bose.com</margeServerUrl></SoundTouchSdkPrivateCfg>"
m.NewSSH = func(host string) SSHClient {
return &mockSSH{
runFunc: func(command string) (string, error) {
if command == fmt.Sprintf("[ -f %s ]", SoundTouchSdkPrivateCfgOverridePath) {
return "", fmt.Errorf("exit status 1")
}
if command == fmt.Sprintf("cat %s", SoundTouchSdkPrivateCfgOverridePath) {
return fmt.Sprintf("cat: can't open '%s': No such file or directory\n", SoundTouchSdkPrivateCfgOverridePath),
fmt.Errorf("exit status 1")
}
if strings.HasPrefix(command, "[ -f ") && strings.Contains(command, ".original") {
return "", fmt.Errorf("exit status 1")
}
if command == fmt.Sprintf("cat %s", SoundTouchSdkPrivateCfgPath) {
return originalCfg, nil
}
return "", nil
},
}
}
summary := &MigrationSummary{}
cfg, err := m.checkCurrentConfig(summary, "127.0.0.1")
if err != nil {
t.Fatalf("checkCurrentConfig returned unexpected error: %v", err)
}
if cfg != originalCfg {
t.Errorf("Expected current config to be the original SoundTouchSdkPrivateCfg.xml, got %q", cfg)
}
if strings.Contains(cfg, "No such file or directory") {
t.Errorf("Current config must not contain cat stderr from missing override file: %q", cfg)
}
if !summary.SSHSuccess {
t.Errorf("Expected SSHSuccess to be true when original config is readable")
}
}
func TestMigrateSpeaker_ResolvBlocking(t *testing.T) {
tempDir, err := os.MkdirTemp("", "setup-test")
if err != nil {