Files
Bose-SoundTouch/pkg/service/setup/telnet_urls_test.go
T
Tobias GesellchenandClaude Opus 4.7 d5f9d16e42 feat(setup): per-field telnet URLs with envswitch derivation rule
Refactors telnetURLConfigCommands into a telnetURLs value type with
explicit per-field URLs (Marge, Stats, SwUpdate, BmxRegistry) and adds
telnetURLsFromOptions to resolve those four URLs from a base targetURL
plus optional per-field overrides via the migration options map
(marge_url, stats_url, sw_update_url, bmx_url).

Envswitch derivation rule: arg1 = u.Marge verbatim, arg2 = u.SwUpdate
verbatim. The soundcork case (Marge has /marge appended) is handled
without any branching — envswitch arg1 carries the same suffix and the
parallel persistence layer stays consistent with the runtime layer on
the next reboot.

The default path is unchanged for users who only enter a base URL: all
four fields share targetURL with the canonical /updates/soundtouch and
/bmx/registry/v1/services suffixes. MigrateSpeaker plumbs the options
map through so the existing handler's option dictionary works for telnet
without UI changes; the UI can layer per-field input on top later.

Existing telnet migration tests updated to call the new signature.
TestMigrateViaTelnet_SoundcorkMargeSuffixPropagatesToEnvswitch is the
load-bearing regression test for the derivation rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 00:37:11 +02:00

148 lines
4.4 KiB
Go

package setup
import (
"reflect"
"strings"
"testing"
)
func TestDefaultTelnetURLs_DerivesAllFourFromBase(t *testing.T) {
got := defaultTelnetURLs("http://example:8000")
want := telnetURLs{
Marge: "http://example:8000",
Stats: "http://example:8000",
SwUpdate: "http://example:8000/updates/soundtouch",
BmxRegistry: "http://example:8000/bmx/registry/v1/services",
}
if !reflect.DeepEqual(got, want) {
t.Errorf("defaultTelnetURLs = %+v, want %+v", got, want)
}
}
func TestTelnetURLsFromOptions_NilOptionsReturnsDefaults(t *testing.T) {
got := telnetURLsFromOptions("http://example:8000", nil)
want := defaultTelnetURLs("http://example:8000")
if !reflect.DeepEqual(got, want) {
t.Errorf("telnetURLsFromOptions(nil) = %+v, want defaults %+v", got, want)
}
}
func TestTelnetURLsFromOptions_EmptyValueFallsBackToDefault(t *testing.T) {
options := map[string]string{
"marge_url": "", // empty override should be ignored
}
got := telnetURLsFromOptions("http://example:8000", options)
if got.Marge != "http://example:8000" {
t.Errorf("Marge with empty override = %q, want default", got.Marge)
}
}
func TestTelnetURLsFromOptions_PerFieldOverrides(t *testing.T) {
options := map[string]string{
"marge_url": "http://example:8000/marge", // soundcork-style
"stats_url": "", // ignored
"sw_update_url": "http://example:8000/custom/updates",
"bmx_url": "http://example:8000/custom/bmx",
}
got := telnetURLsFromOptions("http://example:8000", options)
if got.Marge != "http://example:8000/marge" {
t.Errorf("Marge = %q, want override", got.Marge)
}
if got.Stats != "http://example:8000" {
t.Errorf("Stats = %q, want default (empty override)", got.Stats)
}
if got.SwUpdate != "http://example:8000/custom/updates" {
t.Errorf("SwUpdate = %q, want override", got.SwUpdate)
}
if got.BmxRegistry != "http://example:8000/custom/bmx" {
t.Errorf("BmxRegistry = %q, want override", got.BmxRegistry)
}
}
// TestTelnetURLs_Commands_EnvswitchTracksMargeAndSwUpdate is the load-bearing
// test for the soundcork case: if the user added /marge to Marge, the
// envswitch arg1 must follow the same suffix verbatim, otherwise the
// parallel persistence layer will revert margeServerUrl on next reboot
// (the very failure mode the user described as "envswitch silently
// restores my typo").
func TestTelnetURLs_Commands_EnvswitchTracksMargeAndSwUpdate(t *testing.T) {
urls := telnetURLs{
Marge: "http://example:8000/marge",
Stats: "http://example:8000",
SwUpdate: "http://example:8000/updates/soundtouch",
BmxRegistry: "http://example:8000/bmx/registry/v1/services",
}
cmds := urls.Commands()
var envswitch string
for _, c := range cmds {
if strings.HasPrefix(c, "envswitch boseurls set ") {
envswitch = c
break
}
}
if envswitch == "" {
t.Fatalf("Commands missing envswitch boseurls set:\n%v", cmds)
}
wantEnv := "envswitch boseurls set http://example:8000/marge http://example:8000/updates/soundtouch"
if envswitch != wantEnv {
t.Errorf("envswitch =\n %q\nwant\n %q", envswitch, wantEnv)
}
}
func TestMigrateViaTelnet_SoundcorkMargeSuffixPropagatesToEnvswitch(t *testing.T) {
target := "http://example:8000"
urls := telnetURLs{
Marge: "http://example:8000/marge",
Stats: "http://example:8000",
SwUpdate: "http://example:8000/updates/soundtouch",
BmxRegistry: "http://example:8000/bmx/registry/v1/services",
}
// Build a happy-path responder that matches the *new* command set.
resp := map[string]string{
"sys configuration bmxRegistryUrl " + urls.BmxRegistry: "OK\n",
"sys configuration statsServerUrl " + urls.Stats: "OK\n",
"sys configuration margeServerUrl " + urls.Marge: "OK\n",
"sys configuration swUpdateUrl " + urls.SwUpdate: "OK\n",
"envswitch boseurls set " + urls.Marge + " " + urls.SwUpdate: "OK\n",
"getpdo CurrentSystemConfiguration": "margeServerUrl=" + urls.Marge + "\n",
}
f := &fakeTelnet{responses: resp}
m := newFakeTelnetManager(f)
if _, err := m.migrateViaTelnet("192.0.2.1", target, urls); err != nil {
t.Fatalf("migrateViaTelnet: %v", err)
}
wantEnvCmd := "envswitch boseurls set http://example:8000/marge http://example:8000/updates/soundtouch"
var saw bool
for _, c := range f.commands {
if c == wantEnvCmd {
saw = true
break
}
}
if !saw {
t.Errorf("never sent expected envswitch command %q\nactual commands:\n%v", wantEnvCmd, f.commands)
}
}