mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-19 09:06:14 +00:00
Mirrors the .md/.txt sweep across all tracked _test.go, testdata XML,
and .http integration files. Test files are self-contained (producer
+ assertion in the same file), so the matched-pair swap stays green
under `go test ./...`.
Mapping applied:
192.168.178.[0-9]+ → 192.0.2.[same]
192.168.1.[0-9]+ → 192.0.2.[same]
Sound Machinechen → Living Room SoundTouch
A Sound Machine → Kitchen SoundTouch
A81B6A536A98 + case/separator variants → AABBCCDDEEFF (etc.)
A81B6A849D99 → AABBCCDDEE01
A81B6A849D88 → AABBCCDDEE03
A81B6A536A09 → AABBCCDDEE04
884AEAEEBD27 → AABBCCDDEE02
3230304 → 1000001
9569497 → 1000002
Two semantic fixes alongside the bulk swap:
- pkg/service/zeroconf/zeroconf_test.go: the "private 192" and
"strips query" cases pin acceptance of RFC-1918 192.168/16. They
must use a real 192.168 value; doc-range IPs would (correctly) be
rejected by validateZcBaseURL. Switched to 192.168.10.10 — generic
enough not to match any home LAN default, real enough for the
validator. Added a comment explaining why this single test still
carries a 192.168 literal.
- pkg/service/setup/setup_test.go: TestTestDNSRedirection mocks the
device's `od -An -tu1` byte output, which is space-separated
octets ("192 168 1 100"). My sed only matched the dot-separated
form, so the mock was returning the old IP while the test
assertions had moved to the doc range. Updated to " 192 0 2 100".
go build ./... clean. go test ./... clean (only TestDocsConsistency
remains failing, which is a pre-existing/untracked-file issue).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
182 lines
5.0 KiB
Go
182 lines
5.0 KiB
Go
package setup
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// inspectFakes wires canned XML bodies into Manager.HTTPGet keyed by URL
|
|
// path. A missing path returns 404; an empty-string body returns the
|
|
// supplied err.
|
|
type inspectFakes struct {
|
|
responses map[string]string
|
|
errs map[string]error
|
|
}
|
|
|
|
func (f *inspectFakes) get(url string) (*http.Response, error) {
|
|
for path, body := range f.responses {
|
|
if strings.HasSuffix(url, path) {
|
|
if e := f.errs[path]; e != nil {
|
|
return nil, e
|
|
}
|
|
|
|
return &http.Response{
|
|
StatusCode: 200,
|
|
Body: io.NopCloser(strings.NewReader(body)),
|
|
Header: http.Header{},
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
return &http.Response{
|
|
StatusCode: 404,
|
|
Body: io.NopCloser(strings.NewReader("not found")),
|
|
Header: http.Header{},
|
|
}, nil
|
|
}
|
|
|
|
func TestInspect_HappyPath(t *testing.T) {
|
|
f := &inspectFakes{
|
|
responses: map[string]string{
|
|
"/info": `<info deviceID="506583DE4803">
|
|
<name>Bose SoundTouch DE4803</name>
|
|
<type>SoundTouch 10</type>
|
|
<margeAccountUUID>1234567</margeAccountUUID>
|
|
<margeURL>http://aftertouch.local:8000</margeURL>
|
|
<components>
|
|
<component>
|
|
<componentCategory>SCM</componentCategory>
|
|
<softwareVersion>27.0.6</softwareVersion>
|
|
<serialNumber>F23456789012</serialNumber>
|
|
</component>
|
|
</components>
|
|
</info>`,
|
|
"/networkInfo": `<networkInfo wifiProfileCount="1">
|
|
<interfaces>
|
|
<interface type="WIFI_INTERFACE" name="wlan0" macAddress="aa:bb:cc:dd:ee:ff" ipAddress="192.0.2.42" ssid="MyHomeNetwork" frequencyKHz="2452000" state="NETWORK_WIFI_CONNECTED" signal="GOOD_SIGNAL" mode="STATION"/>
|
|
</interfaces>
|
|
</networkInfo>`,
|
|
"/sources": `<sources><sourceItem source="TUNEIN" status="READY"/><sourceItem source="SPOTIFY" sourceAccount="user@example.com" status="READY"/></sources>`,
|
|
"/presets": `<presets><preset id="1"><ContentItem source="TUNEIN" type="stationurl"><itemName>1LIVE</itemName></ContentItem></preset></presets>`,
|
|
},
|
|
}
|
|
|
|
m := &Manager{HTTPGet: f.get}
|
|
|
|
r := m.Inspect("192.0.2.42", InspectOptions{})
|
|
|
|
if r.InfoErr != nil {
|
|
t.Errorf("InfoErr = %v, want nil", r.InfoErr)
|
|
}
|
|
|
|
if r.Info == nil || r.Info.DeviceID != "506583DE4803" {
|
|
t.Errorf("Info.DeviceID = %v, want 506583DE4803", r.Info)
|
|
}
|
|
|
|
if r.Info.MargeAccountUUID != "1234567" {
|
|
t.Errorf("MargeAccountUUID = %q, want 1234567", r.Info.MargeAccountUUID)
|
|
}
|
|
|
|
if r.Network == nil || len(r.Network.Interfaces.Interfaces) == 0 {
|
|
t.Fatalf("Network parse failed: %v / %v", r.Network, r.NetworkErr)
|
|
}
|
|
|
|
wifi := r.Network.Interfaces.Interfaces[0]
|
|
if wifi.SSID != "MyHomeNetwork" {
|
|
t.Errorf("SSID = %q, want MyHomeNetwork", wifi.SSID)
|
|
}
|
|
|
|
if r.Sources == nil || len(r.Sources.SourceItem) != 2 {
|
|
t.Errorf("Sources = %v, want 2 entries", r.Sources)
|
|
}
|
|
|
|
if r.Presets == nil || len(r.Presets.Presets) != 1 {
|
|
t.Errorf("Presets = %v, want 1 preset", r.Presets)
|
|
}
|
|
|
|
if r.Presets.Presets[0].ContentItem.ItemName != "1LIVE" {
|
|
t.Errorf("preset name = %q, want 1LIVE", r.Presets.Presets[0].ContentItem.ItemName)
|
|
}
|
|
}
|
|
|
|
func TestInspect_PartialFailureRecordsPerSectionErrors(t *testing.T) {
|
|
// /info ok, /presets returns network error — the rest of the report
|
|
// must still populate.
|
|
f := &inspectFakes{
|
|
responses: map[string]string{
|
|
"/info": `<info deviceID="X"><name>n</name></info>`,
|
|
"/networkInfo": `<networkInfo><interfaces></interfaces></networkInfo>`,
|
|
"/sources": `<sources/>`,
|
|
"/presets": "", // body unused — errs map below triggers error
|
|
},
|
|
errs: map[string]error{
|
|
"/presets": errors.New("connection reset"),
|
|
},
|
|
}
|
|
|
|
m := &Manager{HTTPGet: f.get}
|
|
|
|
r := m.Inspect("192.0.2.42", InspectOptions{})
|
|
|
|
if r.InfoErr != nil {
|
|
t.Errorf("InfoErr = %v, want nil", r.InfoErr)
|
|
}
|
|
|
|
if r.PresetsErr == nil {
|
|
t.Error("expected PresetsErr to be populated")
|
|
}
|
|
|
|
if r.Sources == nil {
|
|
t.Error("Sources should still populate despite PresetsErr")
|
|
}
|
|
}
|
|
|
|
func TestInspect_TelnetRuntimeURLs(t *testing.T) {
|
|
f := &inspectFakes{
|
|
responses: map[string]string{
|
|
"/info": `<info deviceID="X"><name>n</name></info>`,
|
|
},
|
|
}
|
|
|
|
tn := &fakeTelnet{
|
|
responses: map[string]string{
|
|
"getpdo CurrentSystemConfiguration": "margeServerUrl: http://aftertouch.local:8000\nstatsServerUrl: http://aftertouch.local:8000\n",
|
|
},
|
|
}
|
|
|
|
m := &Manager{
|
|
HTTPGet: f.get,
|
|
NewTelnet: func(string) TelnetClient { return tn },
|
|
}
|
|
|
|
r := m.Inspect("192.0.2.42", InspectOptions{IncludeTelnet: true})
|
|
|
|
if r.RuntimeErr != nil {
|
|
t.Errorf("RuntimeErr = %v, want nil", r.RuntimeErr)
|
|
}
|
|
|
|
if !strings.Contains(r.RuntimeURLs, "margeServerUrl") {
|
|
t.Errorf("RuntimeURLs missing expected content: %q", r.RuntimeURLs)
|
|
}
|
|
}
|
|
|
|
func TestInspect_TelnetSkippedWhenOptionDisabled(t *testing.T) {
|
|
f := &inspectFakes{
|
|
responses: map[string]string{
|
|
"/info": `<info deviceID="X"><name>n</name></info>`,
|
|
},
|
|
}
|
|
|
|
m := &Manager{HTTPGet: f.get}
|
|
|
|
r := m.Inspect("192.0.2.42", InspectOptions{IncludeTelnet: false})
|
|
|
|
if r.RuntimeURLs != "" || r.RuntimeErr != nil {
|
|
t.Errorf("telnet runtime fields should be zero when IncludeTelnet=false, got %q / %v",
|
|
r.RuntimeURLs, r.RuntimeErr)
|
|
}
|
|
}
|