Files
Bose-SoundTouch/pkg/service/setup/migration_state_telnet_test.go
Tobias GesellchenandClaude Sonnet 4.6 b0df8ba963 fix(setup): correct false-positive migration detection and plan command errors
- isXMLMigrated and isResolvConfMigrated now guard against empty hostname
  (Go's strings.Contains(s, "") is always true, causing any speaker to
  appear migrated when --service-url has a malformed single-slash scheme)
- renderPlanSteps message no longer claims "and paired" when --include-pair=false
- validateServiceURL rejects malformed service URLs early with a hint
  (e.g. "did you mean https://soundtouch.fritz.box?")
- Generated plan-step commands move --host before the subcommand name
  (urfave/cli/v2 requires global flags before the first subcommand token)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:58:57 +02:00

201 lines
6.3 KiB
Go

package setup
import (
"errors"
"testing"
)
func TestIsTelnetMigrated_TargetHostnamePresent(t *testing.T) {
m := &Manager{ServerURL: "http://example:8000"}
summary := &MigrationSummary{
TelnetVerifiedConfig: "margeServerUrl=http://example:8000\nbmxRegistryUrl=http://example:8000/bmx/registry/v1/services\n",
}
if !m.isTelnetMigrated(summary) {
t.Error("isTelnetMigrated = false, want true when getpdo response contains our hostname")
}
}
func TestIsTelnetMigrated_DifferentHostname(t *testing.T) {
m := &Manager{ServerURL: "http://example:8000"}
summary := &MigrationSummary{
TelnetVerifiedConfig: "margeServerUrl=https://streaming.bose.com\n",
}
if m.isTelnetMigrated(summary) {
t.Error("isTelnetMigrated = true, want false when getpdo response points at the original cloud")
}
}
func TestIsTelnetMigrated_EmptyVerifiedConfig(t *testing.T) {
m := &Manager{ServerURL: "http://example:8000"}
summary := &MigrationSummary{} // TelnetVerifiedConfig empty
if m.isTelnetMigrated(summary) {
t.Error("isTelnetMigrated = true, want false when TelnetVerifiedConfig is empty")
}
}
// TestIsXMLMigrated_MalformedServiceURL guards against the false-positive that
// occurs when --service-url is a malformed URL with an empty hostname (e.g.
// "https:/host" instead of "https://host"). url.Parse succeeds but Hostname()
// returns "", and strings.Contains(anything, "") is always true in Go.
func TestIsXMLMigrated_MalformedServiceURL(t *testing.T) {
m := &Manager{ServerURL: "https:/soundtouch.fritz.box"} // single slash — malformed
summary := &MigrationSummary{
ParsedCurrentConfig: &PrivateCfg{
MargeServerUrl: "https://streaming.bose.com",
},
}
if m.isXMLMigrated(summary) {
t.Error("isXMLMigrated = true, want false when ServerURL has no resolvable hostname")
}
}
// TestCheckIsMigrated_MalformedServiceURL ensures a malformed --service-url
// does not produce IsMigrated=true on an unmigrated speaker.
func TestCheckIsMigrated_MalformedServiceURL(t *testing.T) {
m := &Manager{
ServerURL: "https:/soundtouch.fritz.box", // single slash — malformed
NewSSH: func(string) SSHClient {
return &mockSSH{runFunc: func(string) (string, error) { return "", errors.New("unused") }}
},
}
summary := &MigrationSummary{
SSHSuccess: true,
TelnetVerifiedConfig: "", // empty — not migrated via telnet either
ParsedCurrentConfig: &PrivateCfg{
MargeServerUrl: "https://streaming.bose.com",
},
CurrentResolvConf: "nameserver 8.8.8.8\n",
}
m.checkIsMigrated(summary, "192.0.2.1")
if summary.IsMigrated {
t.Error("IsMigrated = true, want false when service URL is malformed and speaker still points at streaming.bose.com")
}
}
// TestCheckIsMigrated_TelnetOnlyMigratedDevice covers the gap that motivated
// this iteration: SSH is unreachable, but the speaker has been pointed at
// our service via telnet (e.g. a firmware that refuses USB unlock). The
// migration UI must still report IsMigrated: true.
func TestCheckIsMigrated_TelnetOnlyMigratedDevice(t *testing.T) {
m := &Manager{ServerURL: "http://example:8000"}
summary := &MigrationSummary{
SSHSuccess: false,
TelnetVerifiedConfig: "margeServerUrl=http://example:8000\n",
}
m.checkIsMigrated(summary, "192.0.2.1")
if !summary.IsMigrated {
t.Error("IsMigrated = false, want true on a telnet-only migrated device with no SSH")
}
}
// TestCheckIsMigrated_NoTelnetNoSSH ensures we don't false-positive when
// neither transport sees the redirect.
func TestCheckIsMigrated_NoTelnetNoSSH(t *testing.T) {
m := &Manager{ServerURL: "http://example:8000"}
summary := &MigrationSummary{
SSHSuccess: false,
TelnetVerifiedConfig: "", // probe failed
}
m.checkIsMigrated(summary, "192.0.2.1")
if summary.IsMigrated {
t.Error("IsMigrated = true, want false when neither SSH nor telnet sees the redirect")
}
}
// TestCheckIsMigrated_PerAxisBooleansArePopulated locks in that each
// axis is reported individually so the UI can show partial-state cells.
// The mock SSH client claims /etc/hosts has Bose redirects; XML is
// unmigrated; resolv has no marker; telnet sees the redirected URL.
// All four axis flags must reflect their independent verdicts and
// IsMigrated must be the OR.
func TestCheckIsMigrated_PerAxisBooleansArePopulated(t *testing.T) {
m := &Manager{
ServerURL: "http://example:8000",
NewSSH: func(string) SSHClient {
return &mockSSH{
runFunc: func(cmd string) (string, error) {
if cmd == "cat /etc/hosts" {
return "192.0.2.1\tstreaming.bose.com\n", nil
}
return "", errors.New("not implemented in this mock")
},
}
},
}
summary := &MigrationSummary{
SSHSuccess: true,
CACertTrusted: true, // hosts migration requires CA trust
ParsedCurrentConfig: &PrivateCfg{
MargeServerUrl: "https://streaming.bose.com",
},
TelnetVerifiedConfig: "margeServerUrl=http://example:8000\n",
CurrentResolvConf: "nameserver 8.8.8.8\n",
}
m.checkIsMigrated(summary, "192.0.2.1")
if !summary.TelnetMigrated {
t.Error("TelnetMigrated = false, want true (verified config points at example)")
}
if summary.XMLMigrated {
t.Error("XMLMigrated = true, want false (parsed XML still points at streaming.bose.com)")
}
if !summary.HostsMigrated {
t.Error("HostsMigrated = false, want true (mock hosts content has Bose redirect + CA trusted)")
}
if summary.ResolvMigrated {
t.Error("ResolvMigrated = true, want false (no marker, no example hostname)")
}
if !summary.IsMigrated {
t.Error("IsMigrated = false, want true (TelnetMigrated || HostsMigrated)")
}
}
// TestCheckIsMigrated_TelnetSeesOriginalSSHSeesOriginal ensures we don't
// false-positive when both transports report unmigrated state.
func TestCheckIsMigrated_TelnetSeesOriginalSSHSeesOriginal(t *testing.T) {
m := &Manager{
ServerURL: "http://example:8000",
NewSSH: func(string) SSHClient {
return &mockSSH{runFunc: func(string) (string, error) { return "", errors.New("file not found") }}
},
}
summary := &MigrationSummary{
SSHSuccess: true,
TelnetVerifiedConfig: "margeServerUrl=https://streaming.bose.com\n",
ParsedCurrentConfig: &PrivateCfg{
MargeServerUrl: "https://streaming.bose.com",
},
CurrentResolvConf: "nameserver 8.8.8.8\n",
}
m.checkIsMigrated(summary, "192.0.2.1")
if summary.IsMigrated {
t.Error("IsMigrated = true, want false when both SSH and telnet see the original cloud URLs")
}
}