mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-19 00:56:16 +00:00
Per #515 comment 5228449448: on a real Lifestyle console, the same 6 commands (5 sys configuration/envswitch + reboot) sent back-to-back left sshd down after reboot, but succeeded sent one at a time with ~7s gaps — same commands, same order, same device, minutes apart. Sending fast may not let the device fully process one command before the next arrives. Adds --command-delay (setup.DefaultTelnetCommandDelay, 3s), threaded through EnableSSHViaTelnetFullConfig/runTelnetInjection (pause after each of the 5 commands) and runEnableSSHInjection (one more pause before the reboot). 0 restores the old back-to-back behavior. The reporter didn't try to find the true minimum, just confirmed ~7s works and speculated "a second or two may well be enough" — 3s is a middle ground, tunable via the flag if a specific device needs more. Also prints an approximate total for the injection phase up front (6 steps x delay, ~18s at the default) so the command doesn't look hung — separate from the existing --wait message for sshd coming up after reboot, which can take much longer. Refs #515
217 lines
6.7 KiB
Go
217 lines
6.7 KiB
Go
package setup
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestEnableSSHViaTelnet_BuildsInjectedCommand(t *testing.T) {
|
|
const svc = "https://192.0.2.10:8443"
|
|
|
|
want := `envswitch boseurls set "https://192.0.2.10:8443;touch /tmp/remote_services;/etc/init.d/sshd start" "https://192.0.2.10:8443/update"`
|
|
|
|
f := &fakeTelnet{responses: map[string]string{want: "OK\n"}}
|
|
m := newFakeTelnetManager(f)
|
|
|
|
if _, err := m.EnableSSHViaTelnet("192.0.2.10", svc); err != nil {
|
|
t.Fatalf("EnableSSHViaTelnet: %v", err)
|
|
}
|
|
|
|
if len(f.commands) != 1 || f.commands[0] != want {
|
|
t.Errorf("sent %q\n want %q", f.commands, want)
|
|
}
|
|
}
|
|
|
|
func TestEnableSSHViaTelnetFullConfig_BuildsInjectedSequence(t *testing.T) {
|
|
const svc = "https://192.0.2.10:8443"
|
|
|
|
const injected = `https://192.0.2.10:8443;touch /tmp/remote_services;/etc/init.d/sshd start`
|
|
|
|
want := []string{
|
|
`sys configuration bmxRegistryUrl "https://192.0.2.10:8443/bmx/registry/v1/services"`,
|
|
`sys configuration statsServerUrl "https://192.0.2.10:8443"`,
|
|
`sys configuration margeServerUrl "` + injected + `"`,
|
|
`sys configuration swUpdateUrl "https://192.0.2.10:8443/updates/soundtouch"`,
|
|
`envswitch boseurls set "` + injected + `" "https://192.0.2.10:8443/updates/soundtouch"`,
|
|
`getpdo CurrentSystemConfiguration`,
|
|
}
|
|
|
|
resp := map[string]string{}
|
|
for _, c := range want {
|
|
resp[c] = "OK\n"
|
|
}
|
|
|
|
f := &fakeTelnet{responses: resp}
|
|
m := newFakeTelnetManager(f)
|
|
|
|
if _, err := m.EnableSSHViaTelnetFullConfig("192.0.2.10", svc, 0); err != nil {
|
|
t.Fatalf("EnableSSHViaTelnetFullConfig: %v", err)
|
|
}
|
|
|
|
if len(f.commands) != len(want) {
|
|
t.Fatalf("sent %d commands %q\n want %d %q", len(f.commands), f.commands, len(want), want)
|
|
}
|
|
|
|
for i, c := range want {
|
|
if f.commands[i] != c {
|
|
t.Errorf("command %d = %q\n want %q", i, f.commands[i], c)
|
|
}
|
|
}
|
|
}
|
|
|
|
// fullConfigResponses builds the {command: "OK"} map for
|
|
// EnableSSHViaTelnetFullConfig's fixed 6-step sequence (5 commands + the
|
|
// getpdo verification) against svc, matching
|
|
// TestEnableSSHViaTelnetFullConfig_BuildsInjectedSequence's command list.
|
|
func fullConfigResponses(svc string) map[string]string {
|
|
injected := svc + `;touch /tmp/remote_services;/etc/init.d/sshd start`
|
|
|
|
cmds := []string{
|
|
`sys configuration bmxRegistryUrl "` + svc + `/bmx/registry/v1/services"`,
|
|
`sys configuration statsServerUrl "` + svc + `"`,
|
|
`sys configuration margeServerUrl "` + injected + `"`,
|
|
`sys configuration swUpdateUrl "` + svc + `/updates/soundtouch"`,
|
|
`envswitch boseurls set "` + injected + `" "` + svc + `/updates/soundtouch"`,
|
|
`getpdo CurrentSystemConfiguration`,
|
|
}
|
|
|
|
resp := make(map[string]string, len(cmds))
|
|
for _, c := range cmds {
|
|
resp[c] = "OK\n"
|
|
}
|
|
|
|
return resp
|
|
}
|
|
|
|
// TestEnableSSHViaTelnetFullConfig_PausesBetweenCommands is the regression
|
|
// test for #515 comment 5228449448: the same commands sent back-to-back
|
|
// left sshd down on a real device, but succeeded sent one at a time with
|
|
// gaps. Uses a small real duration rather than a fake clock/injectable
|
|
// sleeper — simplest thing that actually proves time.Sleep is in the loop,
|
|
// and small enough (5 gaps x 5ms) not to slow the suite down.
|
|
func TestEnableSSHViaTelnetFullConfig_PausesBetweenCommands(t *testing.T) {
|
|
const svc = "https://192.0.2.10:8443"
|
|
const delay = 5 * time.Millisecond
|
|
|
|
f := &fakeTelnet{responses: fullConfigResponses(svc)}
|
|
m := newFakeTelnetManager(f)
|
|
|
|
start := time.Now()
|
|
|
|
if _, err := m.EnableSSHViaTelnetFullConfig("192.0.2.10", svc, delay); err != nil {
|
|
t.Fatalf("EnableSSHViaTelnetFullConfig: %v", err)
|
|
}
|
|
|
|
elapsed := time.Since(start)
|
|
// 5 real commands = 5 gaps (see runTelnetInjection: delay after each
|
|
// command in the loop, including before the getpdo verification).
|
|
wantMin := 5 * delay
|
|
|
|
if elapsed < wantMin {
|
|
t.Errorf("elapsed %v, want at least %v (delay not applied between commands)", elapsed, wantMin)
|
|
}
|
|
}
|
|
|
|
// TestEnableSSHViaTelnetFullConfig_ZeroDelayIsInstant verifies 0 keeps the
|
|
// old back-to-back behavior — no accidental minimum sleep.
|
|
func TestEnableSSHViaTelnetFullConfig_ZeroDelayIsInstant(t *testing.T) {
|
|
const svc = "https://192.0.2.10:8443"
|
|
|
|
f := &fakeTelnet{responses: fullConfigResponses(svc)}
|
|
m := newFakeTelnetManager(f)
|
|
|
|
start := time.Now()
|
|
|
|
if _, err := m.EnableSSHViaTelnetFullConfig("192.0.2.10", svc, 0); err != nil {
|
|
t.Fatalf("EnableSSHViaTelnetFullConfig: %v", err)
|
|
}
|
|
|
|
if elapsed := time.Since(start); elapsed > 50*time.Millisecond {
|
|
t.Errorf("elapsed %v with a 0 delay, expected near-instant", elapsed)
|
|
}
|
|
}
|
|
|
|
func TestResetBoseURLs_BuildsCleanCommand(t *testing.T) {
|
|
const svc = "https://192.0.2.10:8443"
|
|
|
|
want := `envswitch boseurls set "https://192.0.2.10:8443" "https://192.0.2.10:8443/update"`
|
|
|
|
f := &fakeTelnet{responses: map[string]string{want: "OK\n"}}
|
|
m := newFakeTelnetManager(f)
|
|
|
|
if _, err := m.ResetBoseURLs("192.0.2.10", svc); err != nil {
|
|
t.Fatalf("ResetBoseURLs: %v", err)
|
|
}
|
|
|
|
if len(f.commands) != 1 || f.commands[0] != want {
|
|
t.Errorf("sent %q\n want %q", f.commands, want)
|
|
}
|
|
}
|
|
|
|
func TestSetBoseURLs_RejectsDoubleQuote(t *testing.T) {
|
|
m := newFakeTelnetManager(&fakeTelnet{})
|
|
|
|
if _, err := m.EnableSSHViaTelnet("192.0.2.10", `https://x"evil`); err == nil {
|
|
t.Fatal("expected an error when the service URL contains a double quote")
|
|
}
|
|
}
|
|
|
|
func TestClose17000_RunsFirewallSteps(t *testing.T) {
|
|
var ran []string
|
|
|
|
m := &Manager{NewSSH: func(string) SSHClient {
|
|
return &mockSSH{runFunc: func(cmd string) (string, error) {
|
|
ran = append(ran, cmd)
|
|
return "", nil
|
|
}}
|
|
}}
|
|
|
|
if _, err := m.Close17000("192.0.2.10"); err != nil {
|
|
t.Fatalf("Close17000: %v", err)
|
|
}
|
|
|
|
joined := strings.Join(ran, "\n")
|
|
for _, want := range []string{
|
|
"mount / -o rw,remount",
|
|
block17000Marker,
|
|
"iptables -I INPUT -p tcp --dport 17000 -j DROP",
|
|
"--dport 17000 -i lo -j ACCEPT",
|
|
} {
|
|
if !strings.Contains(joined, want) {
|
|
t.Errorf("Close17000 commands missing %q\nran:\n%s", want, joined)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInstallAuthorizedKey_UploadsKey(t *testing.T) {
|
|
m := &Manager{NewSSH: func(string) SSHClient {
|
|
return &mockSSH{runFunc: func(string) (string, error) { return "", nil }}
|
|
}}
|
|
|
|
if _, err := m.InstallAuthorizedKey("192.0.2.10", " ssh-ed25519 AAAATEST comment "); err != nil {
|
|
t.Fatalf("InstallAuthorizedKey: %v", err)
|
|
}
|
|
// A fresh mockSSH is created per NewSSH call, so re-run against a captured
|
|
// one to assert the upload.
|
|
var captured *mockSSH
|
|
|
|
m.NewSSH = func(string) SSHClient {
|
|
captured = &mockSSH{runFunc: func(string) (string, error) { return "", nil }}
|
|
return captured
|
|
}
|
|
|
|
if _, err := m.InstallAuthorizedKey("192.0.2.10", "ssh-ed25519 AAAATEST comment"); err != nil {
|
|
t.Fatalf("InstallAuthorizedKey: %v", err)
|
|
}
|
|
|
|
got, ok := captured.uploaded["/home/root/.ssh/authorized_keys"]
|
|
if !ok {
|
|
t.Fatal("authorized_keys was not uploaded")
|
|
}
|
|
|
|
if strings.TrimSpace(string(got)) != "ssh-ed25519 AAAATEST comment" {
|
|
t.Errorf("uploaded key = %q", string(got))
|
|
}
|
|
}
|