test: sweep example data in test files to RFC-5737 + placeholders

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>
This commit is contained in:
Tobias Gesellchen
2026-05-17 22:05:13 +02:00
co-authored by Claude Opus 4.7
parent 249c2586e9
commit feadc478d5
81 changed files with 739 additions and 734 deletions
+6 -6
View File
@@ -76,9 +76,9 @@ func TestPropagateAddGroup_BothSucceed(t *testing.T) {
leftClient := newTestGroupClient(leftSrv.URL)
rightClient := newTestGroupClient(rightSrv.URL)
req := sampleGroupRequest("192.168.1.131", "192.168.1.134")
req := sampleGroupRequest("192.0.2.131", "192.0.2.134")
leftOut, rightOut := propagateAddGroup(leftClient, rightClient, "192.168.1.131", "192.168.1.134", req)
leftOut, rightOut := propagateAddGroup(leftClient, rightClient, "192.0.2.131", "192.0.2.134", req)
if leftOut.err != nil {
t.Errorf("LEFT err = %v, want nil", leftOut.err)
@@ -117,8 +117,8 @@ func TestPropagateAddGroup_BothSucceed(t *testing.T) {
}
rightBody := (*rightBodies)[0]
if !strings.Contains(rightBody, "<senderIPAddress>192.168.1.131</senderIPAddress>") {
t.Errorf("RIGHT (slave) body must carry <senderIPAddress>192.168.1.131</senderIPAddress>\nbody:\n%s", rightBody)
if !strings.Contains(rightBody, "<senderIPAddress>192.0.2.131</senderIPAddress>") {
t.Errorf("RIGHT (slave) body must carry <senderIPAddress>192.0.2.131</senderIPAddress>\nbody:\n%s", rightBody)
}
}
@@ -134,9 +134,9 @@ func TestPropagateAddGroup_RightFails(t *testing.T) {
leftClient := newTestGroupClient(leftSrv.URL)
rightClient := newTestGroupClient(rightSrv.URL)
req := sampleGroupRequest("192.168.1.131", "192.168.1.134")
req := sampleGroupRequest("192.0.2.131", "192.0.2.134")
leftOut, rightOut := propagateAddGroup(leftClient, rightClient, "192.168.1.131", "192.168.1.134", req)
leftOut, rightOut := propagateAddGroup(leftClient, rightClient, "192.0.2.131", "192.0.2.134", req)
if leftOut.err != nil {
t.Errorf("LEFT err = %v, want nil", leftOut.err)
+4 -4
View File
@@ -17,7 +17,7 @@ func TestIntrospectCommands(t *testing.T) {
}{
{
name: "introspect service with source flag",
args: []string{"soundtouch-cli", "--host", "192.168.1.100", "source", "introspect", "--source", "SPOTIFY"},
args: []string{"soundtouch-cli", "--host", "192.0.2.100", "source", "introspect", "--source", "SPOTIFY"},
expectedOutput: []string{
"Getting introspect data for SPOTIFY",
"=== SPOTIFY Service Introspect Data ===",
@@ -47,7 +47,7 @@ func TestIntrospectCommands(t *testing.T) {
},
{
name: "introspect spotify convenience command",
args: []string{"soundtouch-cli", "--host", "192.168.1.100", "source", "introspect-spotify"},
args: []string{"soundtouch-cli", "--host", "192.0.2.100", "source", "introspect-spotify"},
expectedOutput: []string{
"Getting Spotify introspect data",
"=== Spotify Service Introspect Data ===",
@@ -60,7 +60,7 @@ func TestIntrospectCommands(t *testing.T) {
},
{
name: "introspect with account parameter",
args: []string{"soundtouch-cli", "--host", "192.168.1.100", "source", "introspect", "--source", "SPOTIFY", "--account", "my_spotify_account"},
args: []string{"soundtouch-cli", "--host", "192.0.2.100", "source", "introspect", "--source", "SPOTIFY", "--account", "my_spotify_account"},
expectedOutput: []string{
"Getting introspect data for SPOTIFY",
"Source Account: my_spotify_account",
@@ -68,7 +68,7 @@ func TestIntrospectCommands(t *testing.T) {
},
{
name: "introspect missing source flag",
args: []string{"soundtouch-cli", "--host", "192.168.1.100", "source", "introspect"},
args: []string{"soundtouch-cli", "--host", "192.0.2.100", "source", "introspect"},
expectError: true,
},
{
+4 -4
View File
@@ -17,7 +17,7 @@ func TestRecentsCommands(t *testing.T) {
}{
{
name: "recents list command",
args: []string{"soundtouch-cli", "--host", "192.168.1.100", "recents", "list"},
args: []string{"soundtouch-cli", "--host", "192.0.2.100", "recents", "list"},
expectedOutput: []string{
"Getting recently played content",
"Recent Items Summary:",
@@ -26,7 +26,7 @@ func TestRecentsCommands(t *testing.T) {
},
{
name: "recents filter by source",
args: []string{"soundtouch-cli", "--host", "192.168.1.100", "recents", "filter", "--source", "SPOTIFY"},
args: []string{"soundtouch-cli", "--host", "192.0.2.100", "recents", "filter", "--source", "SPOTIFY"},
expectedOutput: []string{
"Getting filtered recent content",
"filtered by source: SPOTIFY",
@@ -34,7 +34,7 @@ func TestRecentsCommands(t *testing.T) {
},
{
name: "recents latest command",
args: []string{"soundtouch-cli", "--host", "192.168.1.100", "recents", "latest"},
args: []string{"soundtouch-cli", "--host", "192.0.2.100", "recents", "latest"},
expectedOutput: []string{
"Getting most recent item",
"Most Recent Item:",
@@ -42,7 +42,7 @@ func TestRecentsCommands(t *testing.T) {
},
{
name: "recents stats command",
args: []string{"soundtouch-cli", "--host", "192.168.1.100", "recents", "stats"},
args: []string{"soundtouch-cli", "--host", "192.0.2.100", "recents", "stats"},
expectedOutput: []string{
"Getting recent items statistics",
"Recent Items Statistics",
+5 -5
View File
@@ -169,7 +169,7 @@ func TestBuildPlanSteps_NoOpWhenAlreadyMigratedAndPaired(t *testing.T) {
summary := &setup.MigrationSummary{IsMigrated: true, IsPaired: true, TelnetMigrated: true}
inspect := &setup.InspectReport{Info: &setup.DeviceInfoXML{DeviceID: "AABBCCDDEEFF"}}
steps := buildPlanSteps("192.168.1.42", "http://aftertouch.local:8000", "", true, false, inspect, summary)
steps := buildPlanSteps("192.0.2.42", "http://aftertouch.local:8000", "", true, false, inspect, summary)
if len(steps) != 0 {
t.Errorf("expected no steps for fully-set-up device, got %d:\n%v", len(steps), steps)
@@ -180,7 +180,7 @@ func TestBuildPlanSteps_RecommendsPairWhenMigratedButUnpaired(t *testing.T) {
summary := &setup.MigrationSummary{IsMigrated: true, IsPaired: false, TelnetMigrated: true, TelnetReachable: true}
inspect := &setup.InspectReport{Info: &setup.DeviceInfoXML{DeviceID: "AABBCCDDEEFF"}}
steps := buildPlanSteps("192.168.1.42", "http://aftertouch.local:8000", "", true, false, inspect, summary)
steps := buildPlanSteps("192.0.2.42", "http://aftertouch.local:8000", "", true, false, inspect, summary)
if len(steps) != 1 {
t.Fatalf("expected exactly the pair step, got %d:\n%v", len(steps), steps)
@@ -195,7 +195,7 @@ func TestBuildPlanSteps_MigrateRebootThenPairWhenFresh(t *testing.T) {
summary := &setup.MigrationSummary{TelnetReachable: true, SSHSuccess: false, IsPaired: false}
inspect := &setup.InspectReport{Info: &setup.DeviceInfoXML{DeviceID: "AABBCCDDEEFF"}}
steps := buildPlanSteps("192.168.1.42", "http://aftertouch.local:8000", "", true, false, inspect, summary)
steps := buildPlanSteps("192.0.2.42", "http://aftertouch.local:8000", "", true, false, inspect, summary)
// migrate → reboot → pair. The reboot step exists because envswitch's
// parallel-persistence layer only fully wins on the next boot, and we
@@ -228,7 +228,7 @@ func TestBuildPlanSteps_DNSMethodPrependsCAInstall(t *testing.T) {
}
inspect := &setup.InspectReport{Info: &setup.DeviceInfoXML{DeviceID: "X"}}
steps := buildPlanSteps("192.168.1.42", "http://aftertouch.local:8000", "", false, false, inspect, summary)
steps := buildPlanSteps("192.0.2.42", "http://aftertouch.local:8000", "", false, false, inspect, summary)
if len(steps) < 2 {
t.Fatalf("expected at least install-ca + migrate, got %d steps:\n%v", len(steps), steps)
@@ -256,7 +256,7 @@ func TestBuildPlanSteps_ResetModeIncludesManualNetworkSwitches(t *testing.T) {
}
summary := &setup.MigrationSummary{IsMigrated: true, IsPaired: true} // doesn't matter in reset mode
steps := buildPlanSteps("192.168.1.42", "http://aftertouch.local:8000", "", true, true, inspect, summary)
steps := buildPlanSteps("192.0.2.42", "http://aftertouch.local:8000", "", true, true, inspect, summary)
// Expected sequence in --reset mode:
// factory-reset, manual AP switch, wait-ap, wifi-push, manual home switch,
+32 -32
View File
@@ -14,16 +14,16 @@ func TestParseHostPort(t *testing.T) {
}{
{
name: "IPv4 with port",
input: "192.168.1.10:8090",
input: "192.0.2.10:8090",
defaultPort: 8080,
wantHost: "192.168.1.10",
wantHost: "192.0.2.10",
wantPort: 8090,
},
{
name: "IPv4 without port",
input: "192.168.1.10",
input: "192.0.2.10",
defaultPort: 8080,
wantHost: "192.168.1.10",
wantHost: "192.0.2.10",
wantPort: 8080,
},
{
@@ -63,30 +63,30 @@ func TestParseHostPort(t *testing.T) {
},
{
name: "invalid port - non-numeric",
input: "192.168.1.10:abc",
input: "192.0.2.10:abc",
defaultPort: 8080,
wantHost: "192.168.1.10",
wantHost: "192.0.2.10",
wantPort: 8080,
},
{
name: "invalid port - too high",
input: "192.168.1.10:99999",
input: "192.0.2.10:99999",
defaultPort: 8080,
wantHost: "192.168.1.10",
wantHost: "192.0.2.10",
wantPort: 8080,
},
{
name: "invalid port - zero",
input: "192.168.1.10:0",
input: "192.0.2.10:0",
defaultPort: 8080,
wantHost: "192.168.1.10",
wantHost: "192.0.2.10",
wantPort: 8080,
},
{
name: "invalid port - negative",
input: "192.168.1.10:-123",
input: "192.0.2.10:-123",
defaultPort: 8080,
wantHost: "192.168.1.10",
wantHost: "192.0.2.10",
wantPort: 8080,
},
{
@@ -105,37 +105,37 @@ func TestParseHostPort(t *testing.T) {
},
{
name: "multiple colons - malformed",
input: "192.168.1.100:8090:extra",
input: "192.0.2.100:8090:extra",
defaultPort: 8080,
wantHost: "192.168.1.100:8090:extra",
wantHost: "192.0.2.100:8090:extra",
wantPort: 8080,
},
{
name: "standard SoundTouch default",
input: "192.168.1.10",
input: "192.0.2.10",
defaultPort: 8090,
wantHost: "192.168.1.10",
wantHost: "192.0.2.10",
wantPort: 8090,
},
{
name: "valid high port",
input: "192.168.1.100:65535",
input: "192.0.2.100:65535",
defaultPort: 8080,
wantHost: "192.168.1.100",
wantHost: "192.0.2.100",
wantPort: 65535,
},
{
name: "valid low port",
input: "192.168.1.100:1",
input: "192.0.2.100:1",
defaultPort: 8080,
wantHost: "192.168.1.100",
wantHost: "192.0.2.100",
wantPort: 1,
},
{
name: "real SoundTouch device example",
input: "192.168.1.10:8090",
input: "192.0.2.10:8090",
defaultPort: 8080,
wantHost: "192.168.1.10",
wantHost: "192.0.2.10",
wantPort: 8090,
},
{
@@ -166,8 +166,8 @@ func BenchmarkParseHostPort(b *testing.B) {
name string
input string
}{
{"with_port", "192.168.1.100:8090"},
{"without_port", "192.168.1.100"},
{"with_port", "192.0.2.100:8090"},
{"without_port", "192.0.2.100"},
{"hostname_with_port", "soundtouch.local:8090"},
{"ipv6_with_port", "[::1]:8090"},
}
@@ -193,26 +193,26 @@ func TestParseHostPortSoundTouchScenarios(t *testing.T) {
}{
{
name: "typical_cli_usage",
input: "192.168.1.10:8091",
input: "192.0.2.10:8091",
defaultPort: 8090,
description: "User specifies full host:port",
wantHost: "192.168.1.10",
wantHost: "192.0.2.10",
wantPort: 8091,
},
{
name: "discovery_result_host_only",
input: "192.168.1.10",
input: "192.0.2.10",
defaultPort: 8090,
description: "Discovery returns IP, CLI uses default port",
wantHost: "192.168.1.10",
wantHost: "192.0.2.10",
wantPort: 8090,
},
{
name: "custom_port_override",
input: "192.168.1.100:9000",
input: "192.0.2.100:9000",
defaultPort: 8090,
description: "User overrides default SoundTouch port",
wantHost: "192.168.1.100",
wantHost: "192.0.2.100",
wantPort: 9000,
},
{
@@ -225,10 +225,10 @@ func TestParseHostPortSoundTouchScenarios(t *testing.T) {
},
{
name: "invalid_port_fallback",
input: "192.168.1.10:invalid",
input: "192.0.2.10:invalid",
defaultPort: 8090,
description: "Malformed port should fallback to default",
wantHost: "192.168.1.10",
wantHost: "192.0.2.10",
wantPort: 8090,
},
}
+2 -2
View File
@@ -132,10 +132,10 @@ func TestPUTRenameRoutesToLocalHandler(t *testing.T) {
ts := httptest.NewServer(r)
defer ts.Close()
body := `<?xml version="1.0" encoding="UTF-8" ?><device deviceid="A81B6A536A98"><name>Sound Machinechen</name><macaddress>A81B6A536A98</macaddress></device>`
body := `<?xml version="1.0" encoding="UTF-8" ?><device deviceid="AABBCCDDEEFF"><name>Living Room SoundTouch</name><macaddress>AABBCCDDEEFF</macaddress></device>`
req, err := http.NewRequest(http.MethodPut,
ts.URL+"/streaming/account/1111111/device/A81B6A536A98",
ts.URL+"/streaming/account/1111111/device/AABBCCDDEEFF",
strings.NewReader(body))
if err != nil {
t.Fatalf("build request: %v", err)
+1 -1
View File
@@ -24,7 +24,7 @@ func createTestApp() *WebApp {
Name: "Test Speaker",
Type: "SoundTouch 30",
NetworkInfo: []models.NetworkInfo{
{MacAddress: "TEST123", IPAddress: "192.168.1.100"},
{MacAddress: "TEST123", IPAddress: "192.0.2.100"},
},
}
+5 -5
View File
@@ -14,7 +14,7 @@ func TestResolveBindAddr_PassThrough(t *testing.T) {
"",
"localhost",
"127.0.0.1",
"192.168.1.5",
"192.0.2.5",
"::1",
"definitely-not-an-iface-xyz",
}
@@ -102,21 +102,21 @@ func TestDefaultDiscoveryInterface(t *testing.T) {
name: "explicit interface wins over bind-derived default",
rawInterface: "eth1",
rawBind: "eth0",
resolvedBind: "192.168.1.5",
resolvedBind: "192.0.2.5",
want: "eth1",
},
{
name: "derive from --bind when --bind was an interface name",
rawInterface: "",
rawBind: "eth0",
resolvedBind: "192.168.1.5",
resolvedBind: "192.0.2.5",
want: "eth0",
},
{
name: "no derivation when --bind was an IP literal",
rawInterface: "",
rawBind: "192.168.1.5",
resolvedBind: "192.168.1.5",
rawBind: "192.0.2.5",
resolvedBind: "192.0.2.5",
want: "",
},
{
+1 -1
View File
@@ -128,7 +128,7 @@ func TestDeviceConnection(t *testing.T) {
Name: "Test Speaker",
Type: "SoundTouch 30",
NetworkInfo: []models.NetworkInfo{
{MacAddress: "TEST123", IPAddress: "192.168.1.100"},
{MacAddress: "TEST123", IPAddress: "192.0.2.100"},
},
}
+11 -11
View File
@@ -121,7 +121,7 @@ func TestClient_GetBassCapabilities(t *testing.T) {
{
name: "bass supported device",
responseStatus: http.StatusOK,
responseBody: `<bassCapabilities deviceID="A81B6A536A98">
responseBody: `<bassCapabilities deviceID="AABBCCDDEEFF">
<bassAvailable>true</bassAvailable>
<bassMin>-9</bassMin>
<bassMax>9</bassMax>
@@ -129,7 +129,7 @@ func TestClient_GetBassCapabilities(t *testing.T) {
</bassCapabilities>`,
expectError: false,
expectedCaps: &models.BassCapabilities{
DeviceID: "A81B6A536A98",
DeviceID: "AABBCCDDEEFF",
BassAvailable: true,
BassMin: -9,
BassMax: 9,
@@ -139,7 +139,7 @@ func TestClient_GetBassCapabilities(t *testing.T) {
{
name: "bass not supported device",
responseStatus: http.StatusOK,
responseBody: `<bassCapabilities deviceID="A81B6A536A98">
responseBody: `<bassCapabilities deviceID="AABBCCDDEEFF">
<bassAvailable>false</bassAvailable>
<bassMin>0</bassMin>
<bassMax>0</bassMax>
@@ -147,7 +147,7 @@ func TestClient_GetBassCapabilities(t *testing.T) {
</bassCapabilities>`,
expectError: false,
expectedCaps: &models.BassCapabilities{
DeviceID: "A81B6A536A98",
DeviceID: "AABBCCDDEEFF",
BassAvailable: false,
BassMin: 0,
BassMax: 0,
@@ -306,7 +306,7 @@ func TestClient_GetTrackInfo(t *testing.T) {
{
name: "track info with full details",
responseStatus: http.StatusOK,
responseBody: `<nowPlaying deviceID="A81B6A536A98" source="SPOTIFY">
responseBody: `<nowPlaying deviceID="AABBCCDDEEFF" source="SPOTIFY">
<ContentItem source="SPOTIFY" location="/12345" sourceAccount="user@example.com" isPresetable="true">
<itemName>Test Track</itemName>
</ContentItem>
@@ -317,7 +317,7 @@ func TestClient_GetTrackInfo(t *testing.T) {
</nowPlaying>`,
expectError: false,
expectedInfo: &models.NowPlaying{
DeviceID: "A81B6A536A98",
DeviceID: "AABBCCDDEEFF",
Source: "SPOTIFY",
Track: "Test Track",
Artist: "Test Artist",
@@ -328,7 +328,7 @@ func TestClient_GetTrackInfo(t *testing.T) {
{
name: "radio station info",
responseStatus: http.StatusOK,
responseBody: `<nowPlaying deviceID="A81B6A536A98" source="TUNEIN">
responseBody: `<nowPlaying deviceID="AABBCCDDEEFF" source="TUNEIN">
<ContentItem source="TUNEIN" location="/station123" isPresetable="true">
<itemName>Jazz FM</itemName>
</ContentItem>
@@ -337,7 +337,7 @@ func TestClient_GetTrackInfo(t *testing.T) {
</nowPlaying>`,
expectError: false,
expectedInfo: &models.NowPlaying{
DeviceID: "A81B6A536A98",
DeviceID: "AABBCCDDEEFF",
Source: "TUNEIN",
StationName: "Jazz FM",
PlayStatus: "PLAY_STATE",
@@ -346,7 +346,7 @@ func TestClient_GetTrackInfo(t *testing.T) {
{
name: "standby state",
responseStatus: http.StatusOK,
responseBody: `<nowPlaying deviceID="A81B6A536A98" source="STANDBY">
responseBody: `<nowPlaying deviceID="AABBCCDDEEFF" source="STANDBY">
<ContentItem source="STANDBY">
<itemName>STANDBY</itemName>
</ContentItem>
@@ -354,7 +354,7 @@ func TestClient_GetTrackInfo(t *testing.T) {
</nowPlaying>`,
expectError: false,
expectedInfo: &models.NowPlaying{
DeviceID: "A81B6A536A98",
DeviceID: "AABBCCDDEEFF",
Source: "STANDBY",
PlayStatus: "STOP_STATE",
},
@@ -462,7 +462,7 @@ func TestClient_GetTrackInfo(t *testing.T) {
func TestClient_NewEndpoints_NetworkError(t *testing.T) {
// Create client pointing to non-existent server
config := &Config{
Host: "192.168.1.999", // Invalid IP
Host: "192.0.2.999", // Invalid IP
Port: 8090,
}
client := NewClient(config)
+11 -11
View File
@@ -15,15 +15,15 @@ import (
func TestNewClient(t *testing.T) {
config := &Config{
Host: "192.168.1.100",
Host: "192.0.2.100",
Port: 8090,
Timeout: 15 * time.Second,
}
client := NewClient(config)
if client.baseURL != "http://192.168.1.100:8090" {
t.Errorf("Expected baseURL 'http://192.168.1.100:8090', got '%s'", client.baseURL)
if client.baseURL != "http://192.0.2.100:8090" {
t.Errorf("Expected baseURL 'http://192.0.2.100:8090', got '%s'", client.baseURL)
}
if client.timeout != 15*time.Second {
@@ -37,12 +37,12 @@ func TestNewClient(t *testing.T) {
func TestNewClientWithDefaults(t *testing.T) {
config := &Config{
Host: "192.168.1.100",
Host: "192.0.2.100",
}
client := NewClient(config)
if client.baseURL != "http://192.168.1.100:8090" {
if client.baseURL != "http://192.0.2.100:8090" {
t.Errorf("Expected default port 8090 in baseURL, got '%s'", client.baseURL)
}
@@ -56,9 +56,9 @@ func TestNewClientWithDefaults(t *testing.T) {
}
func TestNewClientFromHost(t *testing.T) {
client := NewClientFromHost("192.168.1.200")
client := NewClientFromHost("192.0.2.200")
expected := "http://192.168.1.200:8090"
expected := "http://192.0.2.200:8090"
if client.baseURL != expected {
t.Errorf("Expected baseURL '%s', got '%s'", expected, client.baseURL)
}
@@ -152,8 +152,8 @@ func TestGetDeviceInfo_Success(t *testing.T) {
t.Errorf("Expected first network type 'SCM', got '%s'", net.Type)
}
if net.IPAddress != "192.168.1.10" {
t.Errorf("Expected IP address '192.168.1.10', got '%s'", net.IPAddress)
if net.IPAddress != "192.0.2.10" {
t.Errorf("Expected IP address '192.0.2.10', got '%s'", net.IPAddress)
}
}
}
@@ -259,8 +259,8 @@ func TestPing_Failure(t *testing.T) {
}
func TestBaseURL(t *testing.T) {
client := NewClientFromHost("192.168.1.100")
expected := "http://192.168.1.100:8090"
client := NewClientFromHost("192.0.2.100")
expected := "http://192.0.2.100:8090"
if client.BaseURL() != expected {
t.Errorf("Expected BaseURL '%s', got '%s'", expected, client.BaseURL())
+15 -15
View File
@@ -13,7 +13,7 @@ import (
func Example() {
// Create a client for your SoundTouch device
config := &client.Config{
Host: "192.168.1.100",
Host: "192.0.2.100",
Port: 8090,
Timeout: 10 * time.Second,
}
@@ -45,7 +45,7 @@ func Example() {
// ExampleClient_GetNowPlaying demonstrates how to get current playback information.
func ExampleClient_GetNowPlaying() {
config := &client.Config{Host: "192.168.1.100"}
config := &client.Config{Host: "192.0.2.100"}
c := client.NewClient(config)
nowPlaying, err := c.GetNowPlaying()
@@ -67,7 +67,7 @@ func ExampleClient_GetNowPlaying() {
// ExampleClient_SetVolume demonstrates volume control with validation.
func ExampleClient_SetVolume() {
config := &client.Config{Host: "192.168.1.100"}
config := &client.Config{Host: "192.0.2.100"}
c := client.NewClient(config)
// Set volume to 75%
@@ -92,7 +92,7 @@ func ExampleClient_SetVolume() {
// ExampleClient_SelectSource demonstrates how to change audio sources.
func ExampleClient_SelectSource() {
config := &client.Config{Host: "192.168.1.100"}
config := &client.Config{Host: "192.0.2.100"}
c := client.NewClient(config)
// Switch to Spotify
@@ -121,7 +121,7 @@ func ExampleClient_SelectSource() {
// ExampleClient_SetBass demonstrates bass control.
func ExampleClient_SetBass() {
config := &client.Config{Host: "192.168.1.100"}
config := &client.Config{Host: "192.0.2.100"}
c := client.NewClient(config)
// Set bass to +3 (range: -9 to +9)
@@ -144,7 +144,7 @@ func ExampleClient_SetBass() {
// ExampleClient_SetBalance demonstrates balance control.
func ExampleClient_SetBalance() {
config := &client.Config{Host: "192.168.1.100"}
config := &client.Config{Host: "192.0.2.100"}
c := client.NewClient(config)
// Set balance slightly to the right (range: -50 to +50)
@@ -167,15 +167,15 @@ func ExampleClient_SetBalance() {
// ExampleClient_SetZone demonstrates multiroom zone management.
func ExampleClient_SetZone() {
config := &client.Config{Host: "192.168.1.100"}
config := &client.Config{Host: "192.0.2.100"}
c := client.NewClient(config)
// Create a zone with multiple speakers
zone := &models.ZoneRequest{
Master: "192.168.1.100",
Master: "192.0.2.100",
Members: []models.MemberEntry{
{IP: "192.168.1.101"},
{IP: "192.168.1.102"},
{IP: "192.0.2.101"},
{IP: "192.0.2.102"},
},
}
@@ -192,7 +192,7 @@ func ExampleClient_SetZone() {
// ExampleClient_GetPresets demonstrates how to retrieve configured presets.
func ExampleClient_GetPresets() {
config := &client.Config{Host: "192.168.1.100"}
config := &client.Config{Host: "192.0.2.100"}
c := client.NewClient(config)
presets, err := c.GetPresets()
@@ -212,7 +212,7 @@ func ExampleClient_GetPresets() {
// ExampleClient_NewWebSocketClient demonstrates WebSocket client creation.
func ExampleClient_NewWebSocketClient() {
config := &client.Config{Host: "192.168.1.100"}
config := &client.Config{Host: "192.0.2.100"}
c := client.NewClient(config)
// Create WebSocket client for real-time events
@@ -236,7 +236,7 @@ func ExampleClient_NewWebSocketClient() {
// ExampleClient_SendKey demonstrates sending key commands.
func ExampleClient_SendKey() {
config := &client.Config{Host: "192.168.1.100"}
config := &client.Config{Host: "192.0.2.100"}
c := client.NewClient(config)
// Send various key commands
@@ -262,7 +262,7 @@ func ExampleClient_SendKey() {
// ExampleClient_GetCapabilities demonstrates how to check device capabilities.
func ExampleClient_GetCapabilities() {
config := &client.Config{Host: "192.168.1.100"}
config := &client.Config{Host: "192.0.2.100"}
c := client.NewClient(config)
capabilities, err := c.GetCapabilities()
@@ -288,7 +288,7 @@ func ExampleClient_GetCapabilities() {
func ExampleClient_GetSupportedURLs_concept() {
// Example of how to use GetSupportedURLs() method
// Note: This example shows the concept but doesn't execute to avoid requiring a real device
config := &client.Config{Host: "192.168.1.100"}
config := &client.Config{Host: "192.0.2.100"}
c := client.NewClient(config)
supportedURLs, err := c.GetSupportedURLs()
+6 -6
View File
@@ -20,15 +20,15 @@ func TestClient_GetGroup_Configured(t *testing.T) {
<groupRole>
<deviceId>9070658C9D4A</deviceId>
<role>LEFT</role>
<ipAddress>192.168.1.131</ipAddress>
<ipAddress>192.0.2.131</ipAddress>
</groupRole>
<groupRole>
<deviceId>F45EAB3115DA</deviceId>
<role>RIGHT</role>
<ipAddress>192.168.1.134</ipAddress>
<ipAddress>192.0.2.134</ipAddress>
</groupRole>
</roles>
<senderIPAddress>192.168.1.131</senderIPAddress>
<senderIPAddress>192.0.2.131</senderIPAddress>
<status>GROUP_OK</status>
</group>`
@@ -121,7 +121,7 @@ func TestClient_AddGroup(t *testing.T) {
got.ID = "9999999"
got.Status = "GROUP_OK"
got.SenderIPAddress = "192.168.1.131"
got.SenderIPAddress = "192.0.2.131"
w.Header().Set("Content-Type", "application/xml")
@@ -135,8 +135,8 @@ func TestClient_AddGroup(t *testing.T) {
MasterDeviceID: "9070658C9D4A",
Roles: models.GroupRoles{
Roles: []models.GroupRole{
{DeviceID: "9070658C9D4A", Role: "LEFT", IPAddress: "192.168.1.131"},
{DeviceID: "F45EAB3115DA", Role: "RIGHT", IPAddress: "192.168.1.134"},
{DeviceID: "9070658C9D4A", Role: "LEFT", IPAddress: "192.0.2.131"},
{DeviceID: "F45EAB3115DA", Role: "RIGHT", IPAddress: "192.0.2.134"},
},
},
}
+2 -2
View File
@@ -181,7 +181,7 @@ func TestClient_Introspect_ErrorCases_Integration(t *testing.T) {
// ExampleClient_Introspect demonstrates how to use the Introspect method
func ExampleClient_Introspect() {
config := &Config{
Host: "192.168.1.100",
Host: "192.0.2.100",
Port: 8090,
}
client := NewClient(config)
@@ -216,7 +216,7 @@ func ExampleClient_Introspect() {
// ExampleClient_IntrospectSpotify demonstrates the Spotify convenience method
func ExampleClient_IntrospectSpotify() {
config := &Config{
Host: "192.168.1.100",
Host: "192.0.2.100",
Port: 8090,
}
client := NewClient(config)
+6 -6
View File
@@ -7,7 +7,7 @@ import (
// ExampleClient_Navigate demonstrates basic navigation of content sources
func ExampleClient_Navigate() {
config := &Config{Host: "192.168.1.100", Port: 8090}
config := &Config{Host: "192.0.2.100", Port: 8090}
client := NewClient(config)
// Navigate TuneIn content
@@ -25,7 +25,7 @@ func ExampleClient_Navigate() {
// ExampleClient_SearchStation demonstrates searching for radio stations
func ExampleClient_SearchStation() {
config := &Config{Host: "192.168.1.100", Port: 8090}
config := &Config{Host: "192.0.2.100", Port: 8090}
client := NewClient(config)
// Search for jazz stations on TuneIn
@@ -49,7 +49,7 @@ func ExampleClient_SearchStation() {
// ExampleClient_AddStation demonstrates adding a station and playing it
func ExampleClient_AddStation() {
config := &Config{Host: "192.168.1.100", Port: 8090}
config := &Config{Host: "192.0.2.100", Port: 8090}
client := NewClient(config)
// First, search for content to get a token
@@ -79,7 +79,7 @@ func ExampleClient_AddStation() {
// Example_navigationWorkflow demonstrates a complete workflow
func Example_navigationWorkflow() {
config := &Config{Host: "192.168.1.100", Port: 8090}
config := &Config{Host: "192.0.2.100", Port: 8090}
client := NewClient(config)
// 1. Search for content
@@ -134,7 +134,7 @@ func Example_navigationWorkflow() {
// ExampleClient_NavigateContainer demonstrates browsing into directories
func ExampleClient_NavigateContainer() {
config := &Config{Host: "192.168.1.100", Port: 8090}
config := &Config{Host: "192.0.2.100", Port: 8090}
client := NewClient(config)
// First, get the stored music library root
@@ -187,7 +187,7 @@ func ExampleClient_NavigateContainer() {
// Example_searchAndPlayWorkflow demonstrates search -> add -> play workflow
func Example_searchAndPlayWorkflow() {
config := &Config{Host: "192.168.1.100", Port: 8090}
config := &Config{Host: "192.0.2.100", Port: 8090}
client := NewClient(config)
searchTerm := "classic rock"
+2 -2
View File
@@ -254,7 +254,7 @@ func TestClient_GetRecents_ErrorConditions(t *testing.T) {
// ExampleClient_GetRecents demonstrates how to use the GetRecents method
func ExampleClient_GetRecents() {
config := &Config{
Host: "192.168.1.100",
Host: "192.0.2.100",
Port: 8090,
}
client := NewClient(config)
@@ -299,7 +299,7 @@ func ExampleClient_GetRecents() {
// ExampleRecentsResponse_filtering demonstrates filtering recent items
func ExampleRecentsResponse_filtering() {
config := &Config{
Host: "192.168.1.100",
Host: "192.0.2.100",
Port: 8090,
}
client := NewClient(config)
@@ -14,7 +14,7 @@ func TestGetServiceAvailability_Integration(t *testing.T) {
// This test requires a real SoundTouch device
// Set the SOUNDTOUCH_HOST environment variable to run this test
// Example: SOUNDTOUCH_HOST=192.168.1.100 go test -v -run TestGetServiceAvailability_Integration
// Example: SOUNDTOUCH_HOST=192.0.2.100 go test -v -run TestGetServiceAvailability_Integration
host := os.Getenv("SOUNDTOUCH_TEST_HOST")
if host == "" {
t.Skip("SOUNDTOUCH_TEST_HOST environment variable not set - skipping integration tests")
+2 -2
View File
@@ -594,7 +594,7 @@ func TestClient_SelectContentItem(t *testing.T) {
SourceAccount: "3f205110-4a57-4e91-810a-123456789012",
IsPresetable: true,
ItemName: "Welcome to the New",
ContainerArt: "http://192.168.1.14:8085/v1/albums/983/image",
ContainerArt: "http://192.0.2.14:8085/v1/albums/983/image",
},
wantError: false,
},
@@ -761,7 +761,7 @@ func TestClient_SelectLocalMusic(t *testing.T) {
location: "album:983",
sourceAccount: "3f205110-4a57-4e91-810a-123456789012",
itemName: "Welcome to the New",
containerArt: "http://192.168.1.14:8085/v1/albums/983/image",
containerArt: "http://192.0.2.14:8085/v1/albums/983/image",
wantError: false,
},
{
+3 -3
View File
@@ -464,7 +464,7 @@ func TestClient_GetNetworkInfo(t *testing.T) {
name: "Successful network info retrieval - WiFi device",
serverResponse: `<networkInfo wifiProfileCount="2">
<interfaces>
<interface type="WIFI_INTERFACE" name="wlan0" macAddress="AA:BB:CC:DD:EE:FF" ipAddress="192.168.1.10" ssid="MyHomeNetwork" frequencyKHz="5500000" state="NETWORK_WIFI_CONNECTED" signal="EXCELLENT_SIGNAL" mode="STATION"/>
<interface type="WIFI_INTERFACE" name="wlan0" macAddress="AA:BB:CC:DD:EE:FF" ipAddress="192.0.2.10" ssid="MyHomeNetwork" frequencyKHz="5500000" state="NETWORK_WIFI_CONNECTED" signal="EXCELLENT_SIGNAL" mode="STATION"/>
<interface type="WIFI_INTERFACE" name="wlan1" macAddress="AA:BB:CC:DD:EE:01" state="NETWORK_WIFI_DISCONNECTED"/>
</interfaces>
</networkInfo>`,
@@ -477,7 +477,7 @@ func TestClient_GetNetworkInfo(t *testing.T) {
name: "Ethernet device",
serverResponse: `<networkInfo wifiProfileCount="3">
<interfaces>
<interface type="ETHERNET_INTERFACE" name="eth0" macAddress="AA:BB:CC:DD:EE:FF" ipAddress="192.168.1.10" state="NETWORK_ETHERNET_CONNECTED"/>
<interface type="ETHERNET_INTERFACE" name="eth0" macAddress="AA:BB:CC:DD:EE:FF" ipAddress="192.0.2.10" state="NETWORK_ETHERNET_CONNECTED"/>
</interfaces>
</networkInfo>`,
statusCode: http.StatusOK,
@@ -557,7 +557,7 @@ func TestClient_SystemEndpoints_Integration(t *testing.T) {
case "/networkInfo":
_, _ = w.Write([]byte(`<networkInfo wifiProfileCount="2">
<interfaces>
<interface type="WIFI_INTERFACE" name="wlan0" macAddress="AA:BB:CC:DD:EE:FF" ipAddress="192.168.1.10" ssid="TestNetwork" frequencyKHz="5500000" state="NETWORK_WIFI_CONNECTED" signal="EXCELLENT_SIGNAL" mode="STATION"/>
<interface type="WIFI_INTERFACE" name="wlan0" macAddress="AA:BB:CC:DD:EE:FF" ipAddress="192.0.2.10" ssid="TestNetwork" frequencyKHz="5500000" state="NETWORK_WIFI_CONNECTED" signal="EXCELLENT_SIGNAL" mode="STATION"/>
</interfaces>
</networkInfo>`))
default:
+2 -2
View File
@@ -18,11 +18,11 @@
<margeURL>https://streaming.bose.com</margeURL>
<networkInfo type="SCM">
<macAddress>AA:BB:CC:DD:EE:FF</macAddress>
<ipAddress>192.168.1.10</ipAddress>
<ipAddress>192.0.2.10</ipAddress>
</networkInfo>
<networkInfo type="SMSC">
<macAddress>AA:BB:CC:DD:EE:FF</macAddress>
<ipAddress>192.168.1.10</ipAddress>
<ipAddress>192.0.2.10</ipAddress>
</networkInfo>
<moduleType>sm2</moduleType>
<variant>rhino</variant>
+2 -2
View File
@@ -26,11 +26,11 @@
<margeURL>https://streaming.bose.com</margeURL>
<networkInfo type="SCM">
<macAddress>AA:BB:CC:DD:EE:FF</macAddress>
<ipAddress>192.168.1.10</ipAddress>
<ipAddress>192.0.2.10</ipAddress>
</networkInfo>
<networkInfo type="SMSC">
<macAddress>AA:BB:CC:DD:EE:01</macAddress>
<ipAddress>192.168.1.10</ipAddress>
<ipAddress>192.0.2.10</ipAddress>
</networkInfo>
<moduleType>scm</moduleType>
<variant>spotty</variant>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<networkInfo wifiProfileCount="3">
<interfaces>
<interface type="ETHERNET_INTERFACE" name="eth0" macAddress="AA:BB:CC:DD:EE:FF" ipAddress="192.168.1.10" state="NETWORK_ETHERNET_CONNECTED"/>
<interface type="ETHERNET_INTERFACE" name="eth0" macAddress="AA:BB:CC:DD:EE:FF" ipAddress="192.0.2.10" state="NETWORK_ETHERNET_CONNECTED"/>
</interfaces>
</networkInfo>
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<networkInfo wifiProfileCount="2">
<interfaces>
<interface type="WIFI_INTERFACE" name="wlan0" macAddress="AA:BB:CC:DD:EE:FF" ipAddress="192.168.1.10" ssid="MyHomeNetwork" frequencyKHz="5500000" state="NETWORK_WIFI_CONNECTED" signal="EXCELLENT_SIGNAL" mode="STATION"/>
<interface type="WIFI_INTERFACE" name="wlan0" macAddress="AA:BB:CC:DD:EE:FF" ipAddress="192.0.2.10" ssid="MyHomeNetwork" frequencyKHz="5500000" state="NETWORK_WIFI_CONNECTED" signal="EXCELLENT_SIGNAL" mode="STATION"/>
<interface type="WIFI_INTERFACE" name="wlan1" macAddress="AA:BB:CC:DD:EE:01" state="NETWORK_WIFI_DISCONNECTED"/>
</interfaces>
</networkInfo>
+1 -1
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<nowPlaying deviceID="A81B6A536A98" source="TUNEIN">
<nowPlaying deviceID="AABBCCDDEEFF" source="TUNEIN">
<ContentItem source="TUNEIN" type="stationurl" location="http://stream.example.com/radio" sourceAccount="" isPresetable="true">
<itemName>Classic Rock 101.5</itemName>
<containerArt>https://cdn-radiotime-logos.tunein.com/s123456q.png</containerArt>
+1 -1
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<nowPlaying deviceID="A81B6A536A98" source="SPOTIFY" sourceAccount="user@example.com">
<nowPlaying deviceID="AABBCCDDEEFF" source="SPOTIFY" sourceAccount="user@example.com">
<ContentItem source="SPOTIFY" type="tracklisturl" location="/playback/container/c3BvdGlmeTphcnRpc3Q6NkF5QVRHZzdtRGdCbFo0TjV1Tm9nMA==" sourceAccount="user@example.com" isPresetable="true">
<itemName>SYML</itemName>
<containerArt>https://i.scdn.co/image/ab67616d0000b273ca6f00df62ef197fdc8af79c</containerArt>
+1 -1
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<sources deviceID="A81B6A536A98">
<sources deviceID="AABBCCDDEEFF">
<sourceItem source="AUX" sourceAccount="AUX" status="READY" isLocal="true" multiroomallowed="true">AUX IN</sourceItem>
<sourceItem source="AIRPLAY" sourceAccount="AirPlayUserName" status="UNAVAILABLE" isLocal="false" multiroomallowed="false">AirPlayUserName</sourceItem>
<sourceItem source="QPLAY" sourceAccount="QPlay1UserName" status="UNAVAILABLE" isLocal="true" multiroomallowed="true">QPlay1UserName</sourceItem>
+12 -12
View File
@@ -120,7 +120,7 @@ func TestDefaultWebSocketConfig(t *testing.T) {
}
func TestNewWebSocketClient(t *testing.T) {
client := NewClientFromHost("192.168.1.10")
client := NewClientFromHost("192.0.2.10")
wsClient := client.NewWebSocketClient(nil)
if wsClient == nil {
@@ -145,7 +145,7 @@ func TestNewWebSocketClient(t *testing.T) {
}
func TestWebSocketClient_SetHandlers(t *testing.T) {
client := NewClientFromHost("192.168.1.10")
client := NewClientFromHost("192.0.2.10")
wsClient := client.NewWebSocketClient(nil)
handlers := &models.WebSocketEventHandlers{
@@ -170,7 +170,7 @@ func TestWebSocketClient_SetHandlers(t *testing.T) {
}
func TestWebSocketClient_IndividualHandlers(t *testing.T) {
client := NewClientFromHost("192.168.1.10")
client := NewClientFromHost("192.0.2.10")
wsClient := client.NewWebSocketClient(nil)
wsClient.OnNowPlaying(func(_ *models.NowPlayingUpdatedEvent) {
@@ -204,7 +204,7 @@ func TestWebSocketClient_IndividualHandlers(t *testing.T) {
}
func TestWebSocketClient_IsConnected(t *testing.T) {
client := NewClientFromHost("192.168.1.10")
client := NewClientFromHost("192.0.2.10")
wsClient := client.NewWebSocketClient(nil)
// Initially should not be connected
@@ -245,7 +245,7 @@ func TestWebSocketClient_ConnectToMockServer(t *testing.T) {
}
func TestWebSocketClient_Disconnect(t *testing.T) {
client := NewClientFromHost("192.168.1.10")
client := NewClientFromHost("192.0.2.10")
wsClient := client.NewWebSocketClient(nil)
// Test disconnect when not connected
@@ -270,7 +270,7 @@ func TestWebSocketClient_Disconnect(t *testing.T) {
}
func TestWebSocketClient_HandleMessage(t *testing.T) {
client := NewClientFromHost("192.168.1.10")
client := NewClientFromHost("192.0.2.10")
wsClient := client.NewWebSocketClient(&WebSocketConfig{
Logger: &mockLogger{},
})
@@ -358,7 +358,7 @@ func TestWebSocketClient_HandleMessage(t *testing.T) {
}
func TestWebSocketClient_HandleUnknownEvent(t *testing.T) {
client := NewClientFromHost("192.168.1.10")
client := NewClientFromHost("192.0.2.10")
logger := &mockLogger{}
wsClient := client.NewWebSocketClient(&WebSocketConfig{
Logger: logger,
@@ -389,7 +389,7 @@ func TestWebSocketClient_HandleUnknownEvent(t *testing.T) {
}
func TestWebSocketClient_SendMessage(t *testing.T) {
client := NewClientFromHost("192.168.1.10")
client := NewClientFromHost("192.0.2.10")
wsClient := client.NewWebSocketClient(nil)
// Test send when not connected
@@ -400,7 +400,7 @@ func TestWebSocketClient_SendMessage(t *testing.T) {
}
func TestWebSocketClient_ConfigValidation(t *testing.T) {
client := NewClientFromHost("192.168.1.10")
client := NewClientFromHost("192.0.2.10")
t.Run("NilConfig", func(t *testing.T) {
wsClient := client.NewWebSocketClient(nil)
@@ -428,7 +428,7 @@ func TestWebSocketClient_ConfigValidation(t *testing.T) {
}
func TestWebSocketClient_ConcurrentAccess(_ *testing.T) {
client := NewClientFromHost("192.168.1.10")
client := NewClientFromHost("192.0.2.10")
wsClient := client.NewWebSocketClient(nil)
// Test concurrent access to handlers
@@ -460,7 +460,7 @@ func TestWebSocketClient_ConcurrentAccess(_ *testing.T) {
// Benchmark tests
func BenchmarkWebSocketClient_HandleMessage(b *testing.B) {
client := NewClientFromHost("192.168.1.10")
client := NewClientFromHost("192.0.2.10")
wsClient := client.NewWebSocketClient(&WebSocketConfig{
Logger: &mockLogger{},
})
@@ -489,7 +489,7 @@ func BenchmarkWebSocketClient_HandleMessage(b *testing.B) {
}
func BenchmarkWebSocketClient_SetHandlers(b *testing.B) {
client := NewClientFromHost("192.168.1.10")
client := NewClientFromHost("192.0.2.10")
wsClient := client.NewWebSocketClient(nil)
handlers := &models.WebSocketEventHandlers{
+22 -22
View File
@@ -24,7 +24,7 @@ func TestClient_AddZoneSlave(t *testing.T) {
name: "successful add zone slave with IP",
masterID: "MASTER123",
slaveID: "SLAVE456",
slaveIP: "192.168.1.101",
slaveIP: "192.0.2.101",
responseStatus: http.StatusOK,
responseBody: `<status>OK</status>`,
expectError: false,
@@ -44,7 +44,7 @@ func TestClient_AddZoneSlave(t *testing.T) {
name: "server error response",
masterID: "MASTER123",
slaveID: "SLAVE456",
slaveIP: "192.168.1.101",
slaveIP: "192.0.2.101",
responseStatus: http.StatusInternalServerError,
responseBody: `<error>Internal Server Error</error>`,
expectError: true,
@@ -54,7 +54,7 @@ func TestClient_AddZoneSlave(t *testing.T) {
name: "empty master device ID",
masterID: "",
slaveID: "SLAVE456",
slaveIP: "192.168.1.101",
slaveIP: "192.0.2.101",
responseStatus: http.StatusOK,
responseBody: `<status>OK</status>`,
expectError: true,
@@ -64,7 +64,7 @@ func TestClient_AddZoneSlave(t *testing.T) {
name: "empty slave device ID",
masterID: "MASTER123",
slaveID: "",
slaveIP: "192.168.1.101",
slaveIP: "192.0.2.101",
responseStatus: http.StatusOK,
responseBody: `<status>OK</status>`,
expectError: true,
@@ -84,7 +84,7 @@ func TestClient_AddZoneSlave(t *testing.T) {
name: "same master and slave device ID",
masterID: "MASTER123",
slaveID: "MASTER123",
slaveIP: "192.168.1.101",
slaveIP: "192.0.2.101",
responseStatus: http.StatusOK,
responseBody: `<status>OK</status>`,
expectError: true,
@@ -218,7 +218,7 @@ func TestClient_RemoveZoneSlave(t *testing.T) {
name: "successful remove zone slave with IP",
masterID: "MASTER123",
slaveID: "SLAVE456",
slaveIP: "192.168.1.101",
slaveIP: "192.0.2.101",
responseStatus: http.StatusOK,
responseBody: `<status>OK</status>`,
expectError: false,
@@ -238,7 +238,7 @@ func TestClient_RemoveZoneSlave(t *testing.T) {
name: "server error response",
masterID: "MASTER123",
slaveID: "SLAVE456",
slaveIP: "192.168.1.101",
slaveIP: "192.0.2.101",
responseStatus: http.StatusBadRequest,
responseBody: `<error>Bad Request</error>`,
expectError: true,
@@ -248,7 +248,7 @@ func TestClient_RemoveZoneSlave(t *testing.T) {
name: "device not found",
masterID: "MASTER123",
slaveID: "NONEXISTENT",
slaveIP: "192.168.1.101",
slaveIP: "192.0.2.101",
responseStatus: http.StatusNotFound,
responseBody: `<error>Device not found</error>`,
expectError: true,
@@ -374,7 +374,7 @@ func TestZoneSlaveRequest_Validation(t *testing.T) {
request: &models.ZoneSlaveRequest{
Master: "MASTER123",
Members: []models.ZoneSlaveEntry{
{DeviceID: "SLAVE456", IP: "192.168.1.101"},
{DeviceID: "SLAVE456", IP: "192.0.2.101"},
},
},
expectError: false,
@@ -394,7 +394,7 @@ func TestZoneSlaveRequest_Validation(t *testing.T) {
request: &models.ZoneSlaveRequest{
Master: "",
Members: []models.ZoneSlaveEntry{
{DeviceID: "SLAVE456", IP: "192.168.1.101"},
{DeviceID: "SLAVE456", IP: "192.0.2.101"},
},
},
expectError: true,
@@ -414,8 +414,8 @@ func TestZoneSlaveRequest_Validation(t *testing.T) {
request: &models.ZoneSlaveRequest{
Master: "MASTER123",
Members: []models.ZoneSlaveEntry{
{DeviceID: "SLAVE456", IP: "192.168.1.101"},
{DeviceID: "SLAVE789", IP: "192.168.1.102"},
{DeviceID: "SLAVE456", IP: "192.0.2.101"},
{DeviceID: "SLAVE789", IP: "192.0.2.102"},
},
},
expectError: true,
@@ -426,7 +426,7 @@ func TestZoneSlaveRequest_Validation(t *testing.T) {
request: &models.ZoneSlaveRequest{
Master: "MASTER123",
Members: []models.ZoneSlaveEntry{
{DeviceID: "", IP: "192.168.1.101"},
{DeviceID: "", IP: "192.0.2.101"},
},
},
expectError: true,
@@ -437,7 +437,7 @@ func TestZoneSlaveRequest_Validation(t *testing.T) {
request: &models.ZoneSlaveRequest{
Master: "MASTER123",
Members: []models.ZoneSlaveEntry{
{DeviceID: "MASTER123", IP: "192.168.1.101"},
{DeviceID: "MASTER123", IP: "192.0.2.101"},
},
},
expectError: true,
@@ -481,7 +481,7 @@ func TestZoneSlaveRequest_Validation(t *testing.T) {
func TestZoneSlaveRequest_HelperMethods(t *testing.T) {
t.Run("GetSlaveDeviceID", func(t *testing.T) {
request := models.NewZoneSlaveRequest("MASTER123")
request.AddSlave("SLAVE456", "192.168.1.101")
request.AddSlave("SLAVE456", "192.0.2.101")
deviceID := request.GetSlaveDeviceID()
if deviceID != "SLAVE456" {
@@ -491,11 +491,11 @@ func TestZoneSlaveRequest_HelperMethods(t *testing.T) {
t.Run("GetSlaveIP", func(t *testing.T) {
request := models.NewZoneSlaveRequest("MASTER123")
request.AddSlave("SLAVE456", "192.168.1.101")
request.AddSlave("SLAVE456", "192.0.2.101")
ip := request.GetSlaveIP()
if ip != "192.168.1.101" {
t.Errorf("Expected IP '192.168.1.101', got '%s'", ip)
if ip != "192.0.2.101" {
t.Errorf("Expected IP '192.0.2.101', got '%s'", ip)
}
})
@@ -510,11 +510,11 @@ func TestZoneSlaveRequest_HelperMethods(t *testing.T) {
t.Run("String representation", func(t *testing.T) {
request := models.NewZoneSlaveRequest("MASTER123")
request.AddSlave("SLAVE456", "192.168.1.101")
request.AddSlave("SLAVE456", "192.0.2.101")
str := request.String()
expected := "Zone slave operation: master=MASTER123, slave=SLAVE456 (192.168.1.101)"
expected := "Zone slave operation: master=MASTER123, slave=SLAVE456 (192.0.2.101)"
if str != expected {
t.Errorf("Expected string '%s', got '%s'", expected, str)
}
@@ -541,13 +541,13 @@ func TestClient_ZoneSlaveOperations_NetworkError(t *testing.T) {
client := NewClient(config)
// Test AddZoneSlave with network error
err := client.AddZoneSlave("MASTER123", "SLAVE456", "192.168.1.101")
err := client.AddZoneSlave("MASTER123", "SLAVE456", "192.0.2.101")
if err == nil {
t.Errorf("Expected network error for AddZoneSlave but got none")
}
// Test RemoveZoneSlave with network error
err = client.RemoveZoneSlave("MASTER123", "SLAVE456", "192.168.1.101")
err = client.RemoveZoneSlave("MASTER123", "SLAVE456", "192.0.2.101")
if err == nil {
t.Errorf("Expected network error for RemoveZoneSlave but got none")
}
+21 -21
View File
@@ -32,8 +32,8 @@ func TestClient_GetZone(t *testing.T) {
name: "Zone with members",
responseXML: `<?xml version="1.0" encoding="UTF-8" ?>
<zone master="ABCD1234EFGH">
<member ipaddress="192.168.1.11">EFGH5678IJKL</member>
<member ipaddress="192.168.1.12">IJKL9012MNOP</member>
<member ipaddress="192.0.2.11">EFGH5678IJKL</member>
<member ipaddress="192.0.2.12">IJKL9012MNOP</member>
</zone>`,
responseStatus: http.StatusOK,
expectError: false,
@@ -117,7 +117,7 @@ func TestClient_SetZone(t *testing.T) {
name: "Valid zone request",
zoneRequest: func() *models.ZoneRequest {
zr := models.NewZoneRequest("ABCD1234EFGH")
zr.AddMember("EFGH5678IJKL", "192.168.1.11")
zr.AddMember("EFGH5678IJKL", "192.0.2.11")
return zr
}(),
@@ -140,7 +140,7 @@ func TestClient_SetZone(t *testing.T) {
name: "Invalid zone request - duplicate device",
zoneRequest: func() *models.ZoneRequest {
zr := models.NewZoneRequest("ABCD1234EFGH")
zr.AddMember("ABCD1234EFGH", "192.168.1.10") // Same as master
zr.AddMember("ABCD1234EFGH", "192.0.2.10") // Same as master
return zr
}(),
@@ -238,8 +238,8 @@ func TestClient_CreateZoneWithIPs(t *testing.T) {
masterDeviceID := "ABCD1234EFGH"
members := map[string]string{
"EFGH5678IJKL": "192.168.1.11",
"IJKL9012MNOP": "192.168.1.12",
"EFGH5678IJKL": "192.0.2.11",
"IJKL9012MNOP": "192.0.2.12",
}
err := client.CreateZoneWithIPs(masterDeviceID, members)
@@ -260,7 +260,7 @@ func TestClient_AddToZone(t *testing.T) {
// Return existing zone
response := `<?xml version="1.0" encoding="UTF-8" ?>
<zone master="ABCD1234EFGH">
<member ipaddress="192.168.1.11">EFGH5678IJKL</member>
<member ipaddress="192.0.2.11">EFGH5678IJKL</member>
</zone>`
w.WriteHeader(http.StatusOK)
@@ -277,7 +277,7 @@ func TestClient_AddToZone(t *testing.T) {
client := createTestClient(server.URL)
err := client.AddToZone("IJKL9012MNOP", "192.168.1.12")
err := client.AddToZone("IJKL9012MNOP", "192.0.2.12")
if err != nil {
t.Errorf("Expected no error, but got: %v", err)
}
@@ -303,8 +303,8 @@ func TestClient_RemoveFromZone(t *testing.T) {
// Return existing zone with members
response := `<?xml version="1.0" encoding="UTF-8" ?>
<zone master="ABCD1234EFGH">
<member ipaddress="192.168.1.11">EFGH5678IJKL</member>
<member ipaddress="192.168.1.12">IJKL9012MNOP</member>
<member ipaddress="192.0.2.11">EFGH5678IJKL</member>
<member ipaddress="192.0.2.12">IJKL9012MNOP</member>
</zone>`
w.WriteHeader(http.StatusOK)
@@ -347,8 +347,8 @@ func TestClient_DissolveZone(t *testing.T) {
// Return existing zone with members
response := `<?xml version="1.0" encoding="UTF-8" ?>
<zone master="ABCD1234EFGH">
<member ipaddress="192.168.1.11">EFGH5678IJKL</member>
<member ipaddress="192.168.1.12">IJKL9012MNOP</member>
<member ipaddress="192.0.2.11">EFGH5678IJKL</member>
<member ipaddress="192.0.2.12">IJKL9012MNOP</member>
</zone>`
w.WriteHeader(http.StatusOK)
@@ -397,7 +397,7 @@ func TestClient_IsInZone(t *testing.T) {
name: "Device in zone",
responseXML: `<?xml version="1.0" encoding="UTF-8" ?>
<zone master="ABCD1234EFGH">
<member ipaddress="192.168.1.11">EFGH5678IJKL</member>
<member ipaddress="192.0.2.11">EFGH5678IJKL</member>
</zone>`,
expectedResult: true,
expectError: false,
@@ -469,7 +469,7 @@ func TestClient_GetZoneStatus(t *testing.T) {
</info>`,
zoneXML: `<?xml version="1.0" encoding="UTF-8" ?>
<zone master="ABCD1234EFGH">
<member ipaddress="192.168.1.11">EFGH5678IJKL</member>
<member ipaddress="192.0.2.11">EFGH5678IJKL</member>
</zone>`,
expectedStatus: models.ZoneStatusMaster,
expectError: false,
@@ -483,7 +483,7 @@ func TestClient_GetZoneStatus(t *testing.T) {
</info>`,
zoneXML: `<?xml version="1.0" encoding="UTF-8" ?>
<zone master="ABCD1234EFGH">
<member ipaddress="192.168.1.11">EFGH5678IJKL</member>
<member ipaddress="192.0.2.11">EFGH5678IJKL</member>
</zone>`,
expectedStatus: models.ZoneStatusSlave,
expectError: false,
@@ -548,8 +548,8 @@ func TestClient_GetZoneMembers(t *testing.T) {
name: "Zone with members",
responseXML: `<?xml version="1.0" encoding="UTF-8" ?>
<zone master="ABCD1234EFGH">
<member ipaddress="192.168.1.11">EFGH5678IJKL</member>
<member ipaddress="192.168.1.12">IJKL9012MNOP</member>
<member ipaddress="192.0.2.11">EFGH5678IJKL</member>
<member ipaddress="192.0.2.12">IJKL9012MNOP</member>
</zone>`,
expectedMembers: []string{"ABCD1234EFGH", "EFGH5678IJKL", "IJKL9012MNOP"},
expectError: false,
@@ -633,7 +633,7 @@ func TestClient_Zone_ErrorHandling(t *testing.T) {
client := createTestClient(server.URL)
err := client.AddToZone("DEVICE456", "192.168.1.10")
err := client.AddToZone("DEVICE456", "192.0.2.10")
if err == nil {
t.Error("Expected error when GetZone fails, but got none")
}
@@ -650,8 +650,8 @@ func BenchmarkClient_GetZone(b *testing.B) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
response := `<?xml version="1.0" encoding="UTF-8" ?>
<zone master="ABCD1234EFGH">
<member ipaddress="192.168.1.11">EFGH5678IJKL</member>
<member ipaddress="192.168.1.12">IJKL9012MNOP</member>
<member ipaddress="192.0.2.11">EFGH5678IJKL</member>
<member ipaddress="192.0.2.12">IJKL9012MNOP</member>
</zone>`
w.Header().Set("Content-Type", "application/xml")
@@ -681,7 +681,7 @@ func BenchmarkClient_SetZone(b *testing.B) {
client := createTestClient(server.URL)
zoneRequest := models.NewZoneRequest("ABCD1234EFGH")
zoneRequest.AddMember("EFGH5678IJKL", "192.168.1.11")
zoneRequest.AddMember("EFGH5678IJKL", "192.0.2.11")
b.ResetTimer()
+38 -38
View File
@@ -110,51 +110,51 @@ func TestLoadFromEnv_WithEnvVars(t *testing.T) {
}
func TestParseDeviceString_HostOnly(t *testing.T) {
device, err := parseDeviceString("192.168.1.100")
device, err := parseDeviceString("192.0.2.100")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if device.Host != "192.168.1.100" {
t.Errorf("Expected host '192.168.1.100', got '%s'", device.Host)
if device.Host != "192.0.2.100" {
t.Errorf("Expected host '192.0.2.100', got '%s'", device.Host)
}
if device.Port != 8090 {
t.Errorf("Expected default port 8090, got %d", device.Port)
}
if device.Name != "SoundTouch-192.168.1.100" {
t.Errorf("Expected default name 'SoundTouch-192.168.1.100', got '%s'", device.Name)
if device.Name != "SoundTouch-192.0.2.100" {
t.Errorf("Expected default name 'SoundTouch-192.0.2.100', got '%s'", device.Name)
}
}
func TestParseDeviceString_HostPort(t *testing.T) {
device, err := parseDeviceString("192.168.1.100:8091")
device, err := parseDeviceString("192.0.2.100:8091")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if device.Host != "192.168.1.100" {
t.Errorf("Expected host '192.168.1.100', got '%s'", device.Host)
if device.Host != "192.0.2.100" {
t.Errorf("Expected host '192.0.2.100', got '%s'", device.Host)
}
if device.Port != 8091 {
t.Errorf("Expected port 8091, got %d", device.Port)
}
if device.Name != "SoundTouch-192.168.1.100" {
t.Errorf("Expected default name 'SoundTouch-192.168.1.100', got '%s'", device.Name)
if device.Name != "SoundTouch-192.0.2.100" {
t.Errorf("Expected default name 'SoundTouch-192.0.2.100', got '%s'", device.Name)
}
}
func TestParseDeviceString_NameHostPort(t *testing.T) {
device, err := parseDeviceString("Living Room@192.168.1.100:8090")
device, err := parseDeviceString("Living Room@192.0.2.100:8090")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if device.Host != "192.168.1.100" {
t.Errorf("Expected host '192.168.1.100', got '%s'", device.Host)
if device.Host != "192.0.2.100" {
t.Errorf("Expected host '192.0.2.100', got '%s'", device.Host)
}
if device.Port != 8090 {
@@ -167,13 +167,13 @@ func TestParseDeviceString_NameHostPort(t *testing.T) {
}
func TestParseDeviceString_NameHost(t *testing.T) {
device, err := parseDeviceString("Kitchen Speaker@192.168.1.101")
device, err := parseDeviceString("Kitchen Speaker@192.0.2.101")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if device.Host != "192.168.1.101" {
t.Errorf("Expected host '192.168.1.101', got '%s'", device.Host)
if device.Host != "192.0.2.101" {
t.Errorf("Expected host '192.0.2.101', got '%s'", device.Host)
}
if device.Port != 8090 {
@@ -186,17 +186,17 @@ func TestParseDeviceString_NameHost(t *testing.T) {
}
func TestParseDeviceString_InvalidPort(t *testing.T) {
_, err := parseDeviceString("192.168.1.100:invalid")
_, err := parseDeviceString("192.0.2.100:invalid")
if err == nil {
t.Error("Expected error for invalid port, got nil")
}
_, err = parseDeviceString("192.168.1.100:0")
_, err = parseDeviceString("192.0.2.100:0")
if err == nil {
t.Error("Expected error for port 0, got nil")
}
_, err = parseDeviceString("192.168.1.100:70000")
_, err = parseDeviceString("192.0.2.100:70000")
if err == nil {
t.Error("Expected error for port > 65535, got nil")
}
@@ -217,7 +217,7 @@ func TestParseDeviceString_EmptyHost(t *testing.T) {
func TestParsePreferredDevices_SingleDevice(t *testing.T) {
clearTestEnvVars()
_ = os.Setenv("PREFERRED_DEVICES", "192.168.1.100")
_ = os.Setenv("PREFERRED_DEVICES", "192.0.2.100")
defer clearTestEnvVars()
@@ -230,15 +230,15 @@ func TestParsePreferredDevices_SingleDevice(t *testing.T) {
t.Errorf("Expected 1 device, got %d", len(devices))
}
if devices[0].Host != "192.168.1.100" {
t.Errorf("Expected host '192.168.1.100', got '%s'", devices[0].Host)
if devices[0].Host != "192.0.2.100" {
t.Errorf("Expected host '192.0.2.100', got '%s'", devices[0].Host)
}
}
func TestParsePreferredDevices_MultipleDevices(t *testing.T) {
clearTestEnvVars()
_ = os.Setenv("PREFERRED_DEVICES", "Living Room@192.168.1.100:8090;Kitchen@192.168.1.101;192.168.1.102:8091")
_ = os.Setenv("PREFERRED_DEVICES", "Living Room@192.0.2.100:8090;Kitchen@192.0.2.101;192.0.2.102:8091")
defer clearTestEnvVars()
@@ -256,8 +256,8 @@ func TestParsePreferredDevices_MultipleDevices(t *testing.T) {
t.Errorf("Expected first device name 'Living Room', got '%s'", devices[0].Name)
}
if devices[0].Host != "192.168.1.100" {
t.Errorf("Expected first device host '192.168.1.100', got '%s'", devices[0].Host)
if devices[0].Host != "192.0.2.100" {
t.Errorf("Expected first device host '192.0.2.100', got '%s'", devices[0].Host)
}
if devices[0].Port != 8090 {
@@ -269,8 +269,8 @@ func TestParsePreferredDevices_MultipleDevices(t *testing.T) {
t.Errorf("Expected second device name 'Kitchen', got '%s'", devices[1].Name)
}
if devices[1].Host != "192.168.1.101" {
t.Errorf("Expected second device host '192.168.1.101', got '%s'", devices[1].Host)
if devices[1].Host != "192.0.2.101" {
t.Errorf("Expected second device host '192.0.2.101', got '%s'", devices[1].Host)
}
if devices[1].Port != 8090 {
@@ -278,12 +278,12 @@ func TestParsePreferredDevices_MultipleDevices(t *testing.T) {
}
// Check third device
if devices[2].Name != "SoundTouch-192.168.1.102" {
if devices[2].Name != "SoundTouch-192.0.2.102" {
t.Errorf("Expected third device default name, got '%s'", devices[2].Name)
}
if devices[2].Host != "192.168.1.102" {
t.Errorf("Expected third device host '192.168.1.102', got '%s'", devices[2].Host)
if devices[2].Host != "192.0.2.102" {
t.Errorf("Expected third device host '192.0.2.102', got '%s'", devices[2].Host)
}
if devices[2].Port != 8091 {
@@ -311,7 +311,7 @@ func TestParsePreferredDevices_EmptyString(t *testing.T) {
func TestParsePreferredDevices_InvalidDevice(t *testing.T) {
clearTestEnvVars()
_ = os.Setenv("PREFERRED_DEVICES", "192.168.1.100:invalid")
_ = os.Setenv("PREFERRED_DEVICES", "192.0.2.100:invalid")
defer clearTestEnvVars()
@@ -324,8 +324,8 @@ func TestParsePreferredDevices_InvalidDevice(t *testing.T) {
func TestGetPreferredDevicesAsDiscovered(t *testing.T) {
config := &Config{
PreferredDevices: []DeviceConfig{
{Name: "Living Room", Host: "192.168.1.100", Port: 8090},
{Name: "Kitchen", Host: "192.168.1.101", Port: 8091},
{Name: "Living Room", Host: "192.0.2.100", Port: 8090},
{Name: "Kitchen", Host: "192.0.2.101", Port: 8091},
},
}
@@ -340,15 +340,15 @@ func TestGetPreferredDevicesAsDiscovered(t *testing.T) {
t.Errorf("Expected name 'Living Room', got '%s'", devices[0].Name)
}
if devices[0].Host != "192.168.1.100" {
t.Errorf("Expected host '192.168.1.100', got '%s'", devices[0].Host)
if devices[0].Host != "192.0.2.100" {
t.Errorf("Expected host '192.0.2.100', got '%s'", devices[0].Host)
}
if devices[0].Port != 8090 {
t.Errorf("Expected port 8090, got %d", devices[0].Port)
}
expectedInfoURL := "http://192.168.1.100:8090/info"
expectedInfoURL := "http://192.0.2.100:8090/info"
if devices[0].InfoURL != expectedInfoURL {
t.Errorf("Expected info URL '%s', got '%s'", expectedInfoURL, devices[0].InfoURL)
}
@@ -360,7 +360,7 @@ func TestValidate_ValidConfig(t *testing.T) {
HTTPTimeout: 10 * time.Second,
CacheTTL: 30 * time.Second,
PreferredDevices: []DeviceConfig{
{Name: "Test", Host: "192.168.1.100", Port: 8090},
{Name: "Test", Host: "192.0.2.100", Port: 8090},
},
}
@@ -414,7 +414,7 @@ func TestValidate_InvalidDevices(t *testing.T) {
t.Error("Expected error for empty host, got nil")
}
config.PreferredDevices[0].Host = "192.168.1.100"
config.PreferredDevices[0].Host = "192.0.2.100"
config.PreferredDevices[0].Port = 0 // Invalid port
err = config.Validate()
+14 -14
View File
@@ -11,7 +11,7 @@ import (
)
func TestDNSDiscovery_Interception(t *testing.T) {
serviceIP := "192.168.1.100"
serviceIP := "192.0.2.100"
upstreamDNS := []string{"8.8.8.8"}
d := NewDNSDiscovery(upstreamDNS, serviceIP)
@@ -65,7 +65,7 @@ func TestDNSDiscovery_Interception(t *testing.T) {
func TestDNSDiscovery_Forwarding(t *testing.T) {
// This test is harder because it needs a real upstream or a mock.
// For now, let's just test that it calls forward and record.
serviceIP := "192.168.1.100"
serviceIP := "192.0.2.100"
upstreamDNS := []string{"127.0.0.1:5353"} // Use a port that is likely closed or we can mock
d := NewDNSDiscovery(upstreamDNS, serviceIP)
@@ -106,7 +106,7 @@ func TestDNSDiscovery_Forwarding(t *testing.T) {
}
func TestDNSDiscovery_StartTCP(t *testing.T) {
serviceIP := "192.168.1.100"
serviceIP := "192.0.2.100"
upstreamDNS := []string{"8.8.8.8"}
d := NewDNSDiscovery(upstreamDNS, serviceIP)
@@ -164,7 +164,7 @@ func TestDNSDiscovery_SelfForwarding(t *testing.T) {
mux.HandleFunc("soundtouch.local.", func(w dns.ResponseWriter, r *dns.Msg) {
m := new(dns.Msg)
m.SetReply(r)
rr, _ := dns.NewRR("soundtouch.local. 60 IN A 192.168.178.10")
rr, _ := dns.NewRR("soundtouch.local. 60 IN A 192.0.2.10")
m.Answer = append(m.Answer, rr)
_ = w.WriteMsg(m)
})
@@ -194,8 +194,8 @@ func TestDNSDiscovery_SelfForwarding(t *testing.T) {
}
if a, ok := rw.msg.Answer[0].(*dns.A); ok {
if a.A.String() != "192.168.178.10" {
t.Errorf("Expected IP 192.168.178.10, got %s", a.A.String())
if a.A.String() != "192.0.2.10" {
t.Errorf("Expected IP 192.0.2.10, got %s", a.A.String())
}
}
@@ -214,7 +214,7 @@ func TestDNSDiscovery_SelfForwarding(t *testing.T) {
}
func TestDNSDiscovery_ForwardLocal(t *testing.T) {
serviceIP := "192.168.1.100"
serviceIP := "192.0.2.100"
upstreamDNS := []string{"127.0.0.1:5356"}
d := NewDNSDiscovery(upstreamDNS, serviceIP)
@@ -227,7 +227,7 @@ func TestDNSDiscovery_ForwardLocal(t *testing.T) {
mux.HandleFunc("someone-else.local.", func(w dns.ResponseWriter, r *dns.Msg) {
m := new(dns.Msg)
m.SetReply(r)
rr, _ := dns.NewRR("someone-else.local. 60 IN A 192.168.1.50")
rr, _ := dns.NewRR("someone-else.local. 60 IN A 192.0.2.50")
m.Answer = append(m.Answer, rr)
_ = w.WriteMsg(m)
})
@@ -255,7 +255,7 @@ func TestDNSDiscovery_ForwardLocal(t *testing.T) {
}
func TestDNSDiscovery_IsRunning(t *testing.T) {
serviceIP := "192.168.1.100"
serviceIP := "192.0.2.100"
upstreamDNS := []string{"8.8.8.8"}
d := NewDNSDiscovery(upstreamDNS, serviceIP)
@@ -301,7 +301,7 @@ func (m *mockResponseWriter) TsigTimersOnly(bool) {}
func (m *mockResponseWriter) Hijack() {}
func TestDNSDiscovery_LogThrottling(t *testing.T) {
d := NewDNSDiscovery([]string{"8.8.8.8"}, "192.168.1.100")
d := NewDNSDiscovery([]string{"8.8.8.8"}, "192.0.2.100")
// Capture log output
var logBuf strings.Builder
@@ -332,7 +332,7 @@ func TestDNSDiscovery_LogThrottling(t *testing.T) {
}
func TestDNSDiscovery_LoopPrevention(t *testing.T) {
serviceIP := "192.168.1.100"
serviceIP := "192.0.2.100"
bindAddr := "127.0.0.1:53"
upstreamDNS := []string{"127.0.0.1:53"}
d := NewDNSDiscovery(upstreamDNS, serviceIP)
@@ -360,7 +360,7 @@ func TestDNSDiscovery_LoopPrevention(t *testing.T) {
}
func TestDNSDiscovery_EmptyUpstream(t *testing.T) {
serviceIP := "192.168.1.100"
serviceIP := "192.0.2.100"
var upstreamDNS []string // Empty upstream
d := NewDNSDiscovery(upstreamDNS, serviceIP)
d.bindAddr = ":53"
@@ -383,7 +383,7 @@ func TestDNSDiscovery_EmptyUpstream(t *testing.T) {
}
func TestDNSDiscovery_ForwardTimeout(t *testing.T) {
serviceIP := "192.168.1.100"
serviceIP := "192.0.2.100"
// Mock server that deliberately delays its response
mux := dns.NewServeMux()
@@ -426,7 +426,7 @@ func TestDNSDiscovery_ForwardTimeout(t *testing.T) {
}
func TestDNSDiscovery_MultipleUpstreams(t *testing.T) {
serviceIP := "192.168.1.100"
serviceIP := "192.0.2.100"
// Mock server 1: returns NXDOMAIN
mux1 := dns.NewServeMux()
+8 -8
View File
@@ -29,8 +29,8 @@ func Example() {
// Example output:
// Found 2 devices:
// - Living Room at 192.168.1.100:8090
// - Kitchen at 192.168.1.101:8090
// - Living Room at 192.0.2.100:8090
// - Kitchen at 192.0.2.101:8090
}
// ExampleService_DiscoverDevices demonstrates discovering devices with timeout.
@@ -61,16 +61,16 @@ func ExampleService_DiscoverDevices() {
// Example output:
// Device: Living Room
// Address: 192.168.1.100:8090
// Address: 192.0.2.100:8090
// Serial: AA123456789
// Location: /device.xml
// Host: 192.168.1.100:8090
// Host: 192.0.2.100:8090
//
// Device: Kitchen
// Address: 192.168.1.101:8090
// Address: 192.0.2.101:8090
// Serial: BB123456789
// Location: /device.xml
// Host: 192.168.1.101:8090
// Host: 192.0.2.101:8090
}
// ExampleUnifiedDiscoveryService_DiscoverDevices demonstrates caching functionality.
@@ -130,7 +130,7 @@ func Example_upnpOnlyDiscovery() {
// Example output:
// UPnP discovered 1 devices:
// - Living Room at 192.168.1.100:8090
// - Living Room at 192.0.2.100:8090
}
// ExampleMDNSDiscoveryService_DiscoverDevices demonstrates mDNS-only discovery.
@@ -152,7 +152,7 @@ func ExampleMDNSDiscoveryService_DiscoverDevices() {
// Example output:
// mDNS discovered 1 devices:
// - Kitchen at 192.168.1.101:8090
// - Kitchen at 192.0.2.101:8090
}
// Example_errorHandling demonstrates proper error handling in discovery.
+6 -6
View File
@@ -18,7 +18,7 @@ func TestEnrichDeviceInfo(t *testing.T) {
<device>
<friendlyName>Sound Machinery</friendlyName>
<modelName>SoundTouch 10</modelName>
<serialNumber>A81B6A536A09</serialNumber>
<serialNumber>AABBCCDDEE04</serialNumber>
</device>
</root>`
@@ -49,8 +49,8 @@ func TestEnrichDeviceInfo(t *testing.T) {
t.Errorf("expected ModelID 'SoundTouch 10', got '%s'", device.ModelID)
}
if device.UPnPSerial != "A81B6A536A09" {
t.Errorf("expected UPnPSerial 'A81B6A536A09', got '%s'", device.UPnPSerial)
if device.UPnPSerial != "AABBCCDDEE04" {
t.Errorf("expected UPnPSerial 'AABBCCDDEE04', got '%s'", device.UPnPSerial)
}
}
@@ -60,7 +60,7 @@ func TestUPnP_Unmarshal(t *testing.T) {
<device>
<friendlyName>Sound Machinery</friendlyName>
<modelName>SoundTouch 10</modelName>
<serialNumber>A81B6A536A09</serialNumber>
<serialNumber>AABBCCDDEE04</serialNumber>
</device>
</root>`
@@ -84,7 +84,7 @@ func TestUPnP_Unmarshal(t *testing.T) {
if upnpRoot.Device.ModelName != "SoundTouch 10" {
t.Errorf("expected ModelName 'SoundTouch 10', got '%s'", upnpRoot.Device.ModelName)
}
if upnpRoot.Device.SerialNumber != "A81B6A536A09" {
t.Errorf("expected SerialNumber 'A81B6A536A09', got '%s'", upnpRoot.Device.SerialNumber)
if upnpRoot.Device.SerialNumber != "AABBCCDDEE04" {
t.Errorf("expected SerialNumber 'AABBCCDDEE04', got '%s'", upnpRoot.Device.SerialNumber)
}
}
+26 -26
View File
@@ -20,7 +20,7 @@ func TestUPnP_EnrichDeviceInfo_RealDeviceXML(t *testing.T) {
</specVersion>
<device>
<deviceType>urn:schemas-upnp-org:device:MediaRenderer:1</deviceType>
<friendlyName>Sound Machinechen</friendlyName>
<friendlyName>Living Room SoundTouch</friendlyName>
<qq:X_QPlay_SoftwareCapability xmlns:qq="http://www.tencent.com">QPlay:2</qq:X_QPlay_SoftwareCapability>
<manufacturer>Bose Corporation</manufacturer>
<manufacturerURL>http://www.bose.com</manufacturerURL>
@@ -28,8 +28,8 @@ func TestUPnP_EnrichDeviceInfo_RealDeviceXML(t *testing.T) {
<modelNumber></modelNumber>
<modelDescription>Bose SoundTouch Wireless Streaming Audio Device</modelDescription>
<modelURL>http://www.bose.com</modelURL>
<serialNumber>A81B6A536A98</serialNumber>
<UDN>uuid:BO5EBO5E-F00D-F00D-FEED-A81B6A536A98</UDN>
<serialNumber>AABBCCDDEEFF</serialNumber>
<UDN>uuid:BO5EBO5E-F00D-F00D-FEED-AABBCCDDEEFF</UDN>
<serviceList>
<service>
<serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType>
@@ -72,7 +72,7 @@ func TestUPnP_EnrichDeviceInfo_RealDeviceXML(t *testing.T) {
// Create a discovered device to enrich
device := &models.DiscoveredDevice{
Host: "192.168.1.100",
Host: "192.0.2.100",
Port: 8091,
Name: "Initial Device Name",
}
@@ -86,13 +86,13 @@ func TestUPnP_EnrichDeviceInfo_RealDeviceXML(t *testing.T) {
}
// Verify that the MAC address was extracted correctly from serialNumber
expectedMAC := "A81B6A536A98"
expectedMAC := "AABBCCDDEEFF"
if device.UPnPSerial != expectedMAC {
t.Errorf("Expected UPnPSerial '%s', got '%s'", expectedMAC, device.UPnPSerial)
}
// Verify other enriched fields
expectedName := "Sound Machinechen"
expectedName := "Living Room SoundTouch"
if device.Name != expectedName {
t.Errorf("Expected Name '%s', got '%s'", expectedName, device.Name)
}
@@ -117,32 +117,32 @@ func TestUPnP_MACAddressDiscovery_Integration(t *testing.T) {
}{
{
name: "StandardMAC",
serialNumberInXML: "A81B6A536A98",
expectedUPnPSerial: "A81B6A536A98",
serialNumberInXML: "AABBCCDDEEFF",
expectedUPnPSerial: "AABBCCDDEEFF",
description: "Standard MAC address format without separators",
},
{
name: "MACWithColons",
serialNumberInXML: "A8:1B:6A:53:6A:98",
expectedUPnPSerial: "A8:1B:6A:53:6A:98",
serialNumberInXML: "AA:BB:CC:DD:EE:FF",
expectedUPnPSerial: "AA:BB:CC:DD:EE:FF",
description: "MAC address with colon separators",
},
{
name: "MACWithDashes",
serialNumberInXML: "A8-1B-6A-53-6A-98",
expectedUPnPSerial: "A8-1B-6A-53-6A-98",
serialNumberInXML: "AA-BB-CC-DD-EE-FF",
expectedUPnPSerial: "AA-BB-CC-DD-EE-FF",
description: "MAC address with dash separators",
},
{
name: "LowercaseMAC",
serialNumberInXML: "a81b6a536a98",
expectedUPnPSerial: "a81b6a536a98",
serialNumberInXML: "aabbccddeeff",
expectedUPnPSerial: "aabbccddeeff",
description: "Lowercase MAC address",
},
{
name: "MixedCaseMAC",
serialNumberInXML: "a81B6a536A98",
expectedUPnPSerial: "a81B6a536A98",
serialNumberInXML: "aaBBccddEEff",
expectedUPnPSerial: "aaBBccddEEff",
description: "Mixed case MAC address",
},
}
@@ -173,7 +173,7 @@ func TestUPnP_MACAddressDiscovery_Integration(t *testing.T) {
// Create and enrich device
device := &models.DiscoveredDevice{
Host: "192.168.1.100",
Host: "192.0.2.100",
Port: 8090,
}
@@ -197,23 +197,23 @@ func TestUPnP_MACAddressDiscovery_Integration(t *testing.T) {
func TestUPnP_URLPattern_Realistic(t *testing.T) {
// Test the exact URL pattern mentioned:
// http://192.168.1.100:8091/XD/BO5EBO5E-F00D-F00D-FEED-A81B6A536A98.xml
// http://192.0.2.100:8091/XD/BO5EBO5E-F00D-F00D-FEED-AABBCCDDEEFF.xml
realDeviceXML := `<?xml version="1.0" encoding="utf-8"?>
<root xmlns="urn:schemas-upnp-org:device-1-0">
<device>
<deviceType>urn:schemas-upnp-org:device:MediaRenderer:1</deviceType>
<friendlyName>Sound Machinechen</friendlyName>
<friendlyName>Living Room SoundTouch</friendlyName>
<manufacturer>Bose Corporation</manufacturer>
<modelName>SoundTouch 10</modelName>
<serialNumber>A81B6A536A98</serialNumber>
<UDN>uuid:BO5EBO5E-F00D-F00D-FEED-A81B6A536A98</UDN>
<serialNumber>AABBCCDDEEFF</serialNumber>
<UDN>uuid:BO5EBO5E-F00D-F00D-FEED-AABBCCDDEEFF</UDN>
</device>
</root>`
// Create server that responds to the specific path
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/XD/BO5EBO5E-F00D-F00D-FEED-A81B6A536A98.xml" {
if r.URL.Path == "/XD/BO5EBO5E-F00D-F00D-FEED-AABBCCDDEEFF.xml" {
w.Header().Set("Content-Type", "text/xml")
fmt.Fprint(w, realDeviceXML)
} else {
@@ -224,13 +224,13 @@ func TestUPnP_URLPattern_Realistic(t *testing.T) {
// Test enrichment using the realistic URL path
device := &models.DiscoveredDevice{
Host: "192.168.1.100",
Host: "192.0.2.100",
Port: 8091,
Name: "Initial Name",
}
service := NewService(5 * time.Second)
locationURL := server.URL + "/XD/BO5EBO5E-F00D-F00D-FEED-A81B6A536A98.xml"
locationURL := server.URL + "/XD/BO5EBO5E-F00D-F00D-FEED-AABBCCDDEEFF.xml"
err := service.EnrichDeviceInfo(device, locationURL)
if err != nil {
@@ -238,7 +238,7 @@ func TestUPnP_URLPattern_Realistic(t *testing.T) {
}
// Verify MAC address extraction
expectedMAC := "A81B6A536A98"
expectedMAC := "AABBCCDDEEFF"
if device.UPnPSerial != expectedMAC {
t.Errorf("Expected MAC '%s', got '%s'", expectedMAC, device.UPnPSerial)
}
@@ -254,7 +254,7 @@ func TestUPnP_URLPattern_Realistic(t *testing.T) {
func TestUPnP_ErrorHandling(t *testing.T) {
service := NewService(5 * time.Second)
device := &models.DiscoveredDevice{
Host: "192.168.1.100",
Host: "192.0.2.100",
Name: "Test Device",
}
+32 -32
View File
@@ -85,15 +85,15 @@ func TestBuildMSearchRequest(t *testing.T) {
func TestParseLocationURL_Valid(t *testing.T) {
service := NewService(1 * time.Second)
location := "http://192.168.1.100:8090/device.xml"
location := "http://192.0.2.100:8090/device.xml"
device, err := service.parseLocationURL(location, "")
if err != nil {
t.Fatalf("Expected no error, got: %v", err)
}
if device.Host != "192.168.1.100" {
t.Errorf("Expected host '192.168.1.100', got '%s'", device.Host)
if device.Host != "192.0.2.100" {
t.Errorf("Expected host '192.0.2.100', got '%s'", device.Host)
}
if device.Port != 8090 {
@@ -108,7 +108,7 @@ func TestParseLocationURL_Valid(t *testing.T) {
t.Error("Expected device name to be set")
}
expectedName := "SoundTouch-192.168.1.100"
expectedName := "SoundTouch-192.0.2.100"
if device.Name != expectedName {
t.Errorf("Expected name '%s', got '%s'", expectedName, device.Name)
}
@@ -119,7 +119,7 @@ func TestParseLocationURL_Invalid(t *testing.T) {
invalidURLs := []string{
"not-a-url",
"ftp://192.168.1.100/device.xml",
"ftp://192.0.2.100/device.xml",
"http://invalid-format",
"",
}
@@ -179,7 +179,7 @@ func TestParseResponse_NotMediaRenderer(t *testing.T) {
Cache-Control: max-age=1800
Date: Mon, 22 Jun 1998 09:55:21 GMT
EXT:
Location: http://192.168.1.100:8090/device.xml
Location: http://192.0.2.100:8090/device.xml
Server: Linux/3.14.0 UPnP/1.0 SomeDevice/1.0
ST: urn:schemas-upnp-org:device:SomeOtherDevice:1
USN: uuid:12345678-1234-5678-9012-123456789012::urn:schemas-upnp-org:device:SomeOtherDevice:1
@@ -260,13 +260,13 @@ func TestCacheOperations(t *testing.T) {
// Add devices to cache
testDevices := []*models.DiscoveredDevice{
{
Host: "192.168.1.100",
Host: "192.0.2.100",
Port: 8090,
Name: "Device 1",
LastSeen: time.Now(),
},
{
Host: "192.168.1.101",
Host: "192.0.2.101",
Port: 8090,
Name: "Device 2",
LastSeen: time.Now(),
@@ -296,7 +296,7 @@ func TestCacheExpiration(t *testing.T) {
// Add device with old timestamp
expiredDevice := &models.DiscoveredDevice{
Host: "192.168.1.100",
Host: "192.0.2.100",
Port: 8090,
Name: "Expired Device",
LastSeen: time.Now().Add(-200 * time.Millisecond), // Expired
@@ -304,7 +304,7 @@ func TestCacheExpiration(t *testing.T) {
// Add device with recent timestamp
freshDevice := &models.DiscoveredDevice{
Host: "192.168.1.101",
Host: "192.0.2.101",
Port: 8090,
Name: "Fresh Device",
LastSeen: time.Now(), // Fresh
@@ -321,7 +321,7 @@ func TestCacheExpiration(t *testing.T) {
t.Errorf("Expected 1 non-expired device, got %d", len(devices))
}
if len(devices) > 0 && devices[0].Host != "192.168.1.101" {
if len(devices) > 0 && devices[0].Host != "192.0.2.101" {
t.Errorf("Expected fresh device, got %s", devices[0].Host)
}
}
@@ -331,7 +331,7 @@ func TestDiscoverDevices_UseCache(t *testing.T) {
// Add fresh device to cache
freshDevice := &models.DiscoveredDevice{
Host: "192.168.1.100",
Host: "192.0.2.100",
Port: 8090,
Name: "Cached Device",
LastSeen: time.Now(),
@@ -352,7 +352,7 @@ func TestDiscoverDevices_UseCache(t *testing.T) {
t.Errorf("Expected 1 cached device, got %d", len(devices))
}
if len(devices) > 0 && devices[0].Host != "192.168.1.100" {
if len(devices) > 0 && devices[0].Host != "192.0.2.100" {
t.Errorf("Expected cached device host, got %s", devices[0].Host)
}
}
@@ -362,7 +362,7 @@ func TestDiscoverDevice_FromCache(t *testing.T) {
// Add device to cache
device := &models.DiscoveredDevice{
Host: "192.168.1.100",
Host: "192.0.2.100",
Port: 8090,
Name: "Test Device",
LastSeen: time.Now(),
@@ -374,13 +374,13 @@ func TestDiscoverDevice_FromCache(t *testing.T) {
defer cancel()
// Discover specific device from cache
foundDevice, err := service.DiscoverDevice(ctx, "192.168.1.100")
foundDevice, err := service.DiscoverDevice(ctx, "192.0.2.100")
if err != nil {
t.Fatalf("Expected no error, got: %v", err)
}
if foundDevice.Host != "192.168.1.100" {
t.Errorf("Expected host '192.168.1.100', got '%s'", foundDevice.Host)
if foundDevice.Host != "192.0.2.100" {
t.Errorf("Expected host '192.0.2.100', got '%s'", foundDevice.Host)
}
if foundDevice.Name != "Test Device" {
@@ -395,12 +395,12 @@ func TestDiscoverDevice_NotFound(t *testing.T) {
defer cancel()
// Try to discover non-existent device
_, err := service.DiscoverDevice(ctx, "192.168.1.999")
_, err := service.DiscoverDevice(ctx, "192.0.2.999")
if err == nil {
t.Error("Expected error for non-existent device, got nil")
}
expectedError := "device with host 192.168.1.999 not found"
expectedError := "device with host 192.0.2.999 not found"
if !contains(err.Error(), expectedError) {
t.Errorf("Expected error to contain '%s', got '%s'", expectedError, err.Error())
}
@@ -412,7 +412,7 @@ func TestNewDiscoveryServiceWithConfig(t *testing.T) {
CacheTTL: 60 * time.Second,
UPnPEnabled: false,
PreferredDevices: []config.DeviceConfig{
{Name: "Test Device", Host: "192.168.1.100", Port: 8090},
{Name: "Test Device", Host: "192.0.2.100", Port: 8090},
},
}
@@ -434,8 +434,8 @@ func TestNewDiscoveryServiceWithConfig(t *testing.T) {
func TestGetConfiguredDevices(t *testing.T) {
cfg := &config.Config{
PreferredDevices: []config.DeviceConfig{
{Name: "Living Room", Host: "192.168.1.100", Port: 8090},
{Name: "Kitchen", Host: "192.168.1.101", Port: 8091},
{Name: "Living Room", Host: "192.0.2.100", Port: 8090},
{Name: "Kitchen", Host: "192.0.2.101", Port: 8091},
},
}
@@ -450,8 +450,8 @@ func TestGetConfiguredDevices(t *testing.T) {
t.Errorf("Expected first device name 'Living Room', got '%s'", devices[0].Name)
}
if devices[0].Host != "192.168.1.100" {
t.Errorf("Expected first device host '192.168.1.100', got '%s'", devices[0].Host)
if devices[0].Host != "192.0.2.100" {
t.Errorf("Expected first device host '192.0.2.100', got '%s'", devices[0].Host)
}
if devices[1].Port != 8091 {
@@ -463,13 +463,13 @@ func TestMergeDevices(t *testing.T) {
service := NewService(5 * time.Second)
existing := []*models.DiscoveredDevice{
{Host: "192.168.1.100", Name: "Device 1", Port: 8090},
{Host: "192.168.1.101", Name: "Device 2", Port: 8090},
{Host: "192.0.2.100", Name: "Device 1", Port: 8090},
{Host: "192.0.2.101", Name: "Device 2", Port: 8090},
}
newDevices := []*models.DiscoveredDevice{
{Host: "192.168.1.101", Name: "Duplicate", Port: 8090}, // Duplicate
{Host: "192.168.1.102", Name: "Device 3", Port: 8090}, // New
{Host: "192.0.2.101", Name: "Duplicate", Port: 8090}, // Duplicate
{Host: "192.0.2.102", Name: "Device 3", Port: 8090}, // New
}
merged := service.mergeDevices(existing, newDevices)
@@ -491,7 +491,7 @@ func TestMergeDevices(t *testing.T) {
}
// Check that all unique hosts are present
expectedHosts := []string{"192.168.1.100", "192.168.1.101", "192.168.1.102"}
expectedHosts := []string{"192.0.2.100", "192.0.2.101", "192.0.2.102"}
for _, expectedHost := range expectedHosts {
if _, exists := hosts[expectedHost]; !exists {
t.Errorf("Expected host %s not found in merged devices", expectedHost)
@@ -503,7 +503,7 @@ func TestDiscoverDevices_ConfiguredOnly(t *testing.T) {
cfg := &config.Config{
UPnPEnabled: false, // Disable UPnP
PreferredDevices: []config.DeviceConfig{
{Name: "Test Device", Host: "192.168.1.100", Port: 8090},
{Name: "Test Device", Host: "192.0.2.100", Port: 8090},
},
}
@@ -523,8 +523,8 @@ func TestDiscoverDevices_ConfiguredOnly(t *testing.T) {
t.Errorf("Expected device name 'Test Device', got '%s'", devices[0].Name)
}
if devices[0].Host != "192.168.1.100" {
t.Errorf("Expected device host '192.168.1.100', got '%s'", devices[0].Host)
if devices[0].Host != "192.0.2.100" {
t.Errorf("Expected device host '192.0.2.100', got '%s'", devices[0].Host)
}
}
+5 -5
View File
@@ -13,9 +13,9 @@ func TestClockDisplay_UnmarshalXML(t *testing.T) {
}{
{
name: "Full clock display configuration",
xmlData: `<clockDisplay deviceID="A81B6A536A98" enabled="true" format="24" brightness="75" autoDim="true" timeZone="America/New_York">Clock Display</clockDisplay>`,
xmlData: `<clockDisplay deviceID="AABBCCDDEEFF" enabled="true" format="24" brightness="75" autoDim="true" timeZone="America/New_York">Clock Display</clockDisplay>`,
expected: ClockDisplay{
DeviceID: "A81B6A536A98",
DeviceID: "AABBCCDDEEFF",
Enabled: true,
Format: "24",
Brightness: 75,
@@ -677,15 +677,15 @@ func TestClockDisplayRequest_MarshalXML_TimezoneOnly(t *testing.T) {
func TestClockDisplay_UnmarshalXML_NestedClockConfig(t *testing.T) {
// The real wire format — what firmware-27 devices emit and accept.
xmlData := `<clockDisplay deviceID="A81B6A536A98"><clockConfig timezoneInfo="Europe/Berlin" userEnable="true" timeFormat="TIME_FORMAT_24HOUR_ID" userOffsetMinute="0" brightnessLevel="70" userUtcTime="0"/></clockDisplay>`
xmlData := `<clockDisplay deviceID="AABBCCDDEEFF"><clockConfig timezoneInfo="Europe/Berlin" userEnable="true" timeFormat="TIME_FORMAT_24HOUR_ID" userOffsetMinute="0" brightnessLevel="70" userUtcTime="0"/></clockDisplay>`
var got ClockDisplay
if err := xml.Unmarshal([]byte(xmlData), &got); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
if got.DeviceID != "A81B6A536A98" {
t.Errorf("DeviceID = %q, want A81B6A536A98", got.DeviceID)
if got.DeviceID != "AABBCCDDEEFF" {
t.Errorf("DeviceID = %q, want AABBCCDDEEFF", got.DeviceID)
}
if got.TimeZone != "Europe/Berlin" {
+10 -10
View File
@@ -7,7 +7,7 @@ import (
)
func TestName_UnmarshalXML(t *testing.T) {
xmlData := `<?xml version="1.0" encoding="UTF-8" ?><name>Sound Machinechen</name>`
xmlData := `<?xml version="1.0" encoding="UTF-8" ?><name>Living Room SoundTouch</name>`
var name Name
@@ -16,16 +16,16 @@ func TestName_UnmarshalXML(t *testing.T) {
t.Fatalf("Failed to unmarshal XML: %v", err)
}
if name.Value != "Sound Machinechen" {
t.Errorf("Expected name 'Sound Machinechen', got '%s'", name.Value)
if name.Value != "Living Room SoundTouch" {
t.Errorf("Expected name 'Living Room SoundTouch', got '%s'", name.Value)
}
if name.GetName() != "Sound Machinechen" {
t.Errorf("Expected GetName() 'Sound Machinechen', got '%s'", name.GetName())
if name.GetName() != "Living Room SoundTouch" {
t.Errorf("Expected GetName() 'Living Room SoundTouch', got '%s'", name.GetName())
}
if name.String() != "Sound Machinechen" {
t.Errorf("Expected String() 'Sound Machinechen', got '%s'", name.String())
if name.String() != "Living Room SoundTouch" {
t.Errorf("Expected String() 'Living Room SoundTouch', got '%s'", name.String())
}
if name.IsEmpty() {
@@ -54,7 +54,7 @@ func TestName_EmptyName(t *testing.T) {
func TestCapabilities_UnmarshalXML(t *testing.T) {
xmlData := `<?xml version="1.0" encoding="UTF-8" ?>
<capabilities deviceID="A81B6A536A98">
<capabilities deviceID="AABBCCDDEEFF">
<networkConfig>
<dualMode>true</dualMode>
<wsapiproxy>true</wsapiproxy>
@@ -82,8 +82,8 @@ func TestCapabilities_UnmarshalXML(t *testing.T) {
}
// Test basic fields
if capabilities.DeviceID != "A81B6A536A98" {
t.Errorf("Expected DeviceID 'A81B6A536A98', got '%s'", capabilities.DeviceID)
if capabilities.DeviceID != "AABBCCDDEEFF" {
t.Errorf("Expected DeviceID 'AABBCCDDEEFF', got '%s'", capabilities.DeviceID)
}
// Test boolean capabilities
+11 -11
View File
@@ -15,7 +15,7 @@ func TestNetworkInformation_UnmarshalXML(t *testing.T) {
name: "WiFi device with connected and disconnected interfaces",
xmlData: `<networkInfo wifiProfileCount="2">
<interfaces>
<interface type="WIFI_INTERFACE" name="wlan0" macAddress="AA:BB:CC:DD:EE:FF" ipAddress="192.168.1.10" ssid="MyHomeNetwork" frequencyKHz="5500000" state="NETWORK_WIFI_CONNECTED" signal="EXCELLENT_SIGNAL" mode="STATION"/>
<interface type="WIFI_INTERFACE" name="wlan0" macAddress="AA:BB:CC:DD:EE:FF" ipAddress="192.0.2.10" ssid="MyHomeNetwork" frequencyKHz="5500000" state="NETWORK_WIFI_CONNECTED" signal="EXCELLENT_SIGNAL" mode="STATION"/>
<interface type="WIFI_INTERFACE" name="wlan1" macAddress="AA:BB:CC:DD:EE:01" state="NETWORK_WIFI_DISCONNECTED"/>
</interfaces>
</networkInfo>`,
@@ -27,7 +27,7 @@ func TestNetworkInformation_UnmarshalXML(t *testing.T) {
Type: "WIFI_INTERFACE",
Name: "wlan0",
MacAddress: "AA:BB:CC:DD:EE:FF",
IPAddress: "192.168.1.10",
IPAddress: "192.0.2.10",
SSID: "MyHomeNetwork",
FrequencyKHz: 5500000,
State: "NETWORK_WIFI_CONNECTED",
@@ -48,7 +48,7 @@ func TestNetworkInformation_UnmarshalXML(t *testing.T) {
name: "Ethernet device",
xmlData: `<networkInfo wifiProfileCount="3">
<interfaces>
<interface type="ETHERNET_INTERFACE" name="eth0" macAddress="AA:BB:CC:DD:EE:FF" ipAddress="192.168.1.10" state="NETWORK_ETHERNET_CONNECTED"/>
<interface type="ETHERNET_INTERFACE" name="eth0" macAddress="AA:BB:CC:DD:EE:FF" ipAddress="192.0.2.10" state="NETWORK_ETHERNET_CONNECTED"/>
</interfaces>
</networkInfo>`,
expected: NetworkInformation{
@@ -59,7 +59,7 @@ func TestNetworkInformation_UnmarshalXML(t *testing.T) {
Type: "ETHERNET_INTERFACE",
Name: "eth0",
MacAddress: "AA:BB:CC:DD:EE:FF",
IPAddress: "192.168.1.10",
IPAddress: "192.0.2.10",
State: "NETWORK_ETHERNET_CONNECTED",
},
},
@@ -83,7 +83,7 @@ func TestNetworkInformation_UnmarshalXML(t *testing.T) {
name: "Multiple WiFi interfaces with different signals",
xmlData: `<networkInfo wifiProfileCount="1">
<interfaces>
<interface type="WIFI_INTERFACE" name="wlan0" macAddress="AA:BB:CC:DD:EE:FF" ipAddress="192.168.1.10" ssid="StrongNetwork" frequencyKHz="2400000" state="NETWORK_WIFI_CONNECTED" signal="GOOD_SIGNAL" mode="STATION"/>
<interface type="WIFI_INTERFACE" name="wlan0" macAddress="AA:BB:CC:DD:EE:FF" ipAddress="192.0.2.10" ssid="StrongNetwork" frequencyKHz="2400000" state="NETWORK_WIFI_CONNECTED" signal="GOOD_SIGNAL" mode="STATION"/>
<interface type="WIFI_INTERFACE" name="wlan1" macAddress="AA:BB:CC:DD:EE:01" ssid="WeakNetwork" frequencyKHz="5200000" state="NETWORK_WIFI_CONNECTED" signal="POOR_SIGNAL" mode="STATION"/>
</interfaces>
</networkInfo>`,
@@ -95,7 +95,7 @@ func TestNetworkInformation_UnmarshalXML(t *testing.T) {
Type: "WIFI_INTERFACE",
Name: "wlan0",
MacAddress: "AA:BB:CC:DD:EE:FF",
IPAddress: "192.168.1.10",
IPAddress: "192.0.2.10",
SSID: "StrongNetwork",
FrequencyKHz: 2400000,
State: "NETWORK_WIFI_CONNECTED",
@@ -594,7 +594,7 @@ func TestNetworkInterface_ValidateAddresses(t *testing.T) {
{
name: "Valid IPv4 and MAC",
iface: NetworkInterface{
IPAddress: "192.168.1.10",
IPAddress: "192.0.2.10",
MacAddress: "AA:BB:CC:DD:EE:FF",
},
validIP: true,
@@ -650,7 +650,7 @@ func TestNetworkInformation_GetConnectedInterfaces(t *testing.T) {
Type: InterfaceTypeWiFi,
State: StateWiFiConnected,
SSID: "WiFiNetwork",
IPAddress: "192.168.1.10",
IPAddress: "192.0.2.10",
MacAddress: "AA:BB:CC:DD:EE:FF",
},
{
@@ -661,7 +661,7 @@ func TestNetworkInformation_GetConnectedInterfaces(t *testing.T) {
{
Type: InterfaceTypeEthernet,
State: StateEthernetConnected,
IPAddress: "192.168.1.11",
IPAddress: "192.0.2.11",
MacAddress: "11:22:33:44:55:66",
},
},
@@ -680,8 +680,8 @@ func TestNetworkInformation_GetConnectedInterfaces(t *testing.T) {
ethIface := networkInfo.GetConnectedEthernetInterface()
if ethIface == nil {
t.Error("Expected to find connected Ethernet interface")
} else if ethIface.IPAddress != "192.168.1.11" {
t.Errorf("Expected Ethernet IP '192.168.1.11', got %q", ethIface.IPAddress)
} else if ethIface.IPAddress != "192.0.2.11" {
t.Errorf("Expected Ethernet IP '192.0.2.11', got %q", ethIface.IPAddress)
}
// Test with no connected interfaces
+5 -5
View File
@@ -182,7 +182,7 @@ func TestRepeatSetting_UnmarshalXML(t *testing.T) {
func TestNowPlaying_UnmarshalXML(t *testing.T) {
xmlData := `<?xml version="1.0" encoding="UTF-8" ?>
<nowPlaying deviceID="A81B6A536A98" source="SPOTIFY" sourceAccount="user@example.com">
<nowPlaying deviceID="AABBCCDDEEFF" source="SPOTIFY" sourceAccount="user@example.com">
<ContentItem source="SPOTIFY" type="tracklisturl" location="/playbook/container/abc123" sourceAccount="user@example.com" isPresetable="true">
<itemName>SYML</itemName>
<containerArt>https://i.scdn.co/image/ab67616d0000b273ca6f00df62ef197fdc8af79c</containerArt>
@@ -211,8 +211,8 @@ func TestNowPlaying_UnmarshalXML(t *testing.T) {
}
// Test basic fields
if nowPlaying.DeviceID != "A81B6A536A98" {
t.Errorf("Expected DeviceID 'A81B6A536A98', got '%s'", nowPlaying.DeviceID)
if nowPlaying.DeviceID != "AABBCCDDEEFF" {
t.Errorf("Expected DeviceID 'AABBCCDDEEFF', got '%s'", nowPlaying.DeviceID)
}
if nowPlaying.Source != "SPOTIFY" {
@@ -309,7 +309,7 @@ func TestNowPlaying_UnmarshalXML(t *testing.T) {
func TestNowPlaying_RadioStation(t *testing.T) {
xmlData := `<?xml version="1.0" encoding="UTF-8" ?>
<nowPlaying deviceID="A81B6A536A98" source="TUNEIN">
<nowPlaying deviceID="AABBCCDDEEFF" source="TUNEIN">
<stationName>Classic Rock 101.5</stationName>
<description>The Best Classic Rock Hits</description>
<stationLocation>New York, NY</stationLocation>
@@ -343,7 +343,7 @@ func TestNowPlaying_RadioStation(t *testing.T) {
func TestNowPlaying_EmptyState(t *testing.T) {
xmlData := `<?xml version="1.0" encoding="UTF-8" ?>
<nowPlaying deviceID="A81B6A536A98" source="">
<nowPlaying deviceID="AABBCCDDEEFF" source="">
<playStatus>STOP_STATE</playStatus>
</nowPlaying>`
+3 -3
View File
@@ -201,7 +201,7 @@ func TestSourceItem_Methods(t *testing.T) {
func TestSources_UnmarshalXML(t *testing.T) {
xmlData := `<?xml version="1.0" encoding="UTF-8" ?>
<sources deviceID="A81B6A536A98">
<sources deviceID="AABBCCDDEEFF">
<sourceItem source="AUX" sourceAccount="AUX" status="READY" isLocal="true" multiroomallowed="true">AUX IN</sourceItem>
<sourceItem source="SPOTIFY" sourceAccount="user@example.com" status="READY" isLocal="false" multiroomallowed="true">user+spotify@example.com</sourceItem>
<sourceItem source="BLUETOOTH" status="UNAVAILABLE" isLocal="true" multiroomallowed="true" />
@@ -216,8 +216,8 @@ func TestSources_UnmarshalXML(t *testing.T) {
}
// Test basic fields
if sources.DeviceID != "A81B6A536A98" {
t.Errorf("Expected DeviceID 'A81B6A536A98', got '%s'", sources.DeviceID)
if sources.DeviceID != "AABBCCDDEEFF" {
t.Errorf("Expected DeviceID 'AABBCCDDEEFF', got '%s'", sources.DeviceID)
}
if len(sources.SourceItem) != 4 {
+2 -2
View File
@@ -202,12 +202,12 @@ func TestParseWebSocketEvent(t *testing.T) {
<groupRole>
<deviceId>9070658C9D4A</deviceId>
<role>LEFT</role>
<ipAddress>192.168.1.131</ipAddress>
<ipAddress>192.0.2.131</ipAddress>
</groupRole>
<groupRole>
<deviceId>F45EAB3115DA</deviceId>
<role>RIGHT</role>
<ipAddress>192.168.1.134</ipAddress>
<ipAddress>192.0.2.134</ipAddress>
</groupRole>
</roles>
<status>GROUP_OK</status>
+22 -22
View File
@@ -22,7 +22,7 @@ func TestZoneSlaveRequest_Creation(t *testing.T) {
t.Run("AddSlave", func(t *testing.T) {
request := NewZoneSlaveRequest("MASTER123")
request.AddSlave("SLAVE456", "192.168.1.101")
request.AddSlave("SLAVE456", "192.0.2.101")
if len(request.Members) != 1 {
t.Errorf("Expected 1 member, got %d", len(request.Members))
@@ -34,8 +34,8 @@ func TestZoneSlaveRequest_Creation(t *testing.T) {
t.Errorf("Expected device ID 'SLAVE456', got '%s'", member.DeviceID)
}
if member.IP != "192.168.1.101" {
t.Errorf("Expected IP '192.168.1.101', got '%s'", member.IP)
if member.IP != "192.0.2.101" {
t.Errorf("Expected IP '192.0.2.101', got '%s'", member.IP)
}
})
}
@@ -52,7 +52,7 @@ func TestZoneSlaveRequest_Validation(t *testing.T) {
name: "valid request with IP",
masterID: "MASTER123",
members: []ZoneSlaveEntry{
{DeviceID: "SLAVE456", IP: "192.168.1.101"},
{DeviceID: "SLAVE456", IP: "192.0.2.101"},
},
expectError: false,
},
@@ -67,7 +67,7 @@ func TestZoneSlaveRequest_Validation(t *testing.T) {
{
name: "empty master ID",
masterID: "",
members: []ZoneSlaveEntry{{DeviceID: "SLAVE456", IP: "192.168.1.101"}},
members: []ZoneSlaveEntry{{DeviceID: "SLAVE456", IP: "192.0.2.101"}},
expectError: true,
errorMsg: "master device ID is required",
},
@@ -82,8 +82,8 @@ func TestZoneSlaveRequest_Validation(t *testing.T) {
name: "multiple members",
masterID: "MASTER123",
members: []ZoneSlaveEntry{
{DeviceID: "SLAVE456", IP: "192.168.1.101"},
{DeviceID: "SLAVE789", IP: "192.168.1.102"},
{DeviceID: "SLAVE456", IP: "192.0.2.101"},
{DeviceID: "SLAVE789", IP: "192.0.2.102"},
},
expectError: true,
errorMsg: "zone slave operations require exactly one member",
@@ -91,14 +91,14 @@ func TestZoneSlaveRequest_Validation(t *testing.T) {
{
name: "empty slave device ID",
masterID: "MASTER123",
members: []ZoneSlaveEntry{{DeviceID: "", IP: "192.168.1.101"}},
members: []ZoneSlaveEntry{{DeviceID: "", IP: "192.0.2.101"}},
expectError: true,
errorMsg: "slave device ID cannot be empty",
},
{
name: "same master and slave ID",
masterID: "MASTER123",
members: []ZoneSlaveEntry{{DeviceID: "MASTER123", IP: "192.168.1.101"}},
members: []ZoneSlaveEntry{{DeviceID: "MASTER123", IP: "192.0.2.101"}},
expectError: true,
errorMsg: "slave device ID cannot be the same as master",
},
@@ -148,7 +148,7 @@ func TestZoneSlaveRequest_Validation(t *testing.T) {
func TestZoneSlaveRequest_HelperMethods(t *testing.T) {
t.Run("GetSlaveDeviceID with member", func(t *testing.T) {
request := NewZoneSlaveRequest("MASTER123")
request.AddSlave("SLAVE456", "192.168.1.101")
request.AddSlave("SLAVE456", "192.0.2.101")
deviceID := request.GetSlaveDeviceID()
@@ -169,11 +169,11 @@ func TestZoneSlaveRequest_HelperMethods(t *testing.T) {
t.Run("GetSlaveIP with member", func(t *testing.T) {
request := NewZoneSlaveRequest("MASTER123")
request.AddSlave("SLAVE456", "192.168.1.101")
request.AddSlave("SLAVE456", "192.0.2.101")
ip := request.GetSlaveIP()
expected := "192.168.1.101"
expected := "192.0.2.101"
if ip != expected {
t.Errorf("Expected IP '%s', got '%s'", expected, ip)
}
@@ -209,11 +209,11 @@ func TestZoneSlaveRequest_String(t *testing.T) {
name: "with IP address",
setup: func() *ZoneSlaveRequest {
req := NewZoneSlaveRequest("MASTER123")
req.AddSlave("SLAVE456", "192.168.1.101")
req.AddSlave("SLAVE456", "192.0.2.101")
return req
},
expected: "Zone slave operation: master=MASTER123, slave=SLAVE456 (192.168.1.101)",
expected: "Zone slave operation: master=MASTER123, slave=SLAVE456 (192.0.2.101)",
},
{
name: "without IP address",
@@ -249,7 +249,7 @@ func TestZoneSlaveRequest_String(t *testing.T) {
func TestZoneSlaveRequest_XMLMarshaling(t *testing.T) {
t.Run("marshal with IP", func(t *testing.T) {
request := NewZoneSlaveRequest("MASTER123")
request.AddSlave("SLAVE456", "192.168.1.101")
request.AddSlave("SLAVE456", "192.0.2.101")
xmlData, err := xml.Marshal(request)
if err != nil {
@@ -263,7 +263,7 @@ func TestZoneSlaveRequest_XMLMarshaling(t *testing.T) {
t.Error("Expected XML to contain zone element with master attribute")
}
if !strings.Contains(xmlStr, `<member ipaddress="192.168.1.101">SLAVE456</member>`) {
if !strings.Contains(xmlStr, `<member ipaddress="192.0.2.101">SLAVE456</member>`) {
t.Error("Expected XML to contain member with IP address")
}
})
@@ -304,11 +304,11 @@ func TestZoneSlaveRequest_XMLUnmarshaling(t *testing.T) {
}{
{
name: "valid XML with IP",
xmlData: `<zone master="MASTER123"><member ipaddress="192.168.1.101">SLAVE456</member></zone>`,
xmlData: `<zone master="MASTER123"><member ipaddress="192.0.2.101">SLAVE456</member></zone>`,
expectedReq: &ZoneSlaveRequest{
Master: "MASTER123",
Members: []ZoneSlaveEntry{
{DeviceID: "SLAVE456", IP: "192.168.1.101"},
{DeviceID: "SLAVE456", IP: "192.0.2.101"},
},
},
expectError: false,
@@ -378,7 +378,7 @@ func TestZoneSlaveEntry_XMLMarshaling(t *testing.T) {
t.Run("entry with IP", func(t *testing.T) {
entry := ZoneSlaveEntry{
DeviceID: "SLAVE456",
IP: "192.168.1.101",
IP: "192.0.2.101",
}
xmlData, err := xml.Marshal(entry)
@@ -388,7 +388,7 @@ func TestZoneSlaveEntry_XMLMarshaling(t *testing.T) {
xmlStr := string(xmlData)
expected := `<member ipaddress="192.168.1.101">SLAVE456</member>`
expected := `<member ipaddress="192.0.2.101">SLAVE456</member>`
if xmlStr != expected {
t.Errorf("Expected XML '%s', got '%s'", expected, xmlStr)
}
@@ -417,8 +417,8 @@ func TestZoneSlaveEntry_XMLMarshaling(t *testing.T) {
func TestZoneSlaveRequest_EdgeCases(t *testing.T) {
t.Run("multiple AddSlave calls", func(t *testing.T) {
request := NewZoneSlaveRequest("MASTER123")
request.AddSlave("SLAVE456", "192.168.1.101")
request.AddSlave("SLAVE789", "192.168.1.102")
request.AddSlave("SLAVE456", "192.0.2.101")
request.AddSlave("SLAVE789", "192.0.2.102")
if len(request.Members) != 2 {
t.Errorf("Expected 2 members, got %d", len(request.Members))
+40 -40
View File
@@ -22,8 +22,8 @@ func TestNewZoneRequest(t *testing.T) {
func TestZoneRequest_AddMember(t *testing.T) {
zr := NewZoneRequest("MASTER123")
zr.AddMember("DEVICE456", "192.168.1.10")
zr.AddMember("DEVICE789", "192.168.1.11")
zr.AddMember("DEVICE456", "192.0.2.10")
zr.AddMember("DEVICE789", "192.0.2.11")
if len(zr.Members) != 2 {
t.Errorf("Expected 2 members, got %d", len(zr.Members))
@@ -33,8 +33,8 @@ func TestZoneRequest_AddMember(t *testing.T) {
t.Errorf("Expected first member DEVICE456, got %s", zr.Members[0].DeviceID)
}
if zr.Members[0].IP != "192.168.1.10" {
t.Errorf("Expected first member IP 192.168.1.10, got %s", zr.Members[0].IP)
if zr.Members[0].IP != "192.0.2.10" {
t.Errorf("Expected first member IP 192.0.2.10, got %s", zr.Members[0].IP)
}
}
@@ -58,9 +58,9 @@ func TestZoneRequest_AddMemberByDeviceID(t *testing.T) {
func TestZoneRequest_RemoveMember(t *testing.T) {
zr := NewZoneRequest("MASTER123")
zr.AddMember("DEVICE456", "192.168.1.10")
zr.AddMember("DEVICE789", "192.168.1.11")
zr.AddMember("DEVICEABC", "192.168.1.12")
zr.AddMember("DEVICE456", "192.0.2.10")
zr.AddMember("DEVICE789", "192.0.2.11")
zr.AddMember("DEVICEABC", "192.0.2.12")
// Remove middle member
zr.RemoveMember("DEVICE789")
@@ -86,8 +86,8 @@ func TestZoneRequest_RemoveMember(t *testing.T) {
func TestZoneRequest_ClearMembers(t *testing.T) {
zr := NewZoneRequest("MASTER123")
zr.AddMember("DEVICE456", "192.168.1.10")
zr.AddMember("DEVICE789", "192.168.1.11")
zr.AddMember("DEVICE456", "192.0.2.10")
zr.AddMember("DEVICE789", "192.0.2.11")
zr.ClearMembers()
@@ -98,7 +98,7 @@ func TestZoneRequest_ClearMembers(t *testing.T) {
func TestZoneRequest_HasMember(t *testing.T) {
zr := NewZoneRequest("MASTER123")
zr.AddMember("DEVICE456", "192.168.1.10")
zr.AddMember("DEVICE456", "192.0.2.10")
if !zr.HasMember("DEVICE456") {
t.Error("Expected HasMember to return true for DEVICE456")
@@ -120,13 +120,13 @@ func TestZoneRequest_GetMemberCount(t *testing.T) {
t.Errorf("Expected 0 members initially, got %d", zr.GetMemberCount())
}
zr.AddMember("DEVICE456", "192.168.1.10")
zr.AddMember("DEVICE456", "192.0.2.10")
if zr.GetMemberCount() != 1 {
t.Errorf("Expected 1 member, got %d", zr.GetMemberCount())
}
zr.AddMember("DEVICE789", "192.168.1.11")
zr.AddMember("DEVICE789", "192.0.2.11")
if zr.GetMemberCount() != 2 {
t.Errorf("Expected 2 members, got %d", zr.GetMemberCount())
@@ -144,7 +144,7 @@ func TestZoneRequest_Validate(t *testing.T) {
name: "Valid zone request",
setupFunc: func() *ZoneRequest {
zr := NewZoneRequest("MASTER123")
zr.AddMember("DEVICE456", "192.168.1.10")
zr.AddMember("DEVICE456", "192.0.2.10")
return zr
},
@@ -184,8 +184,8 @@ func TestZoneRequest_Validate(t *testing.T) {
name: "Duplicate member device ID",
setupFunc: func() *ZoneRequest {
zr := NewZoneRequest("MASTER123")
zr.AddMember("DEVICE456", "192.168.1.10")
zr.AddMember("DEVICE456", "192.168.1.11")
zr.AddMember("DEVICE456", "192.0.2.10")
zr.AddMember("DEVICE456", "192.0.2.11")
return zr
},
@@ -196,7 +196,7 @@ func TestZoneRequest_Validate(t *testing.T) {
name: "Master device ID as member",
setupFunc: func() *ZoneRequest {
zr := NewZoneRequest("MASTER123")
zr.AddMember("MASTER123", "192.168.1.10")
zr.AddMember("MASTER123", "192.0.2.10")
return zr
},
@@ -320,8 +320,8 @@ func TestZoneInfo_GetMemberByDeviceID(t *testing.T) {
zi := &ZoneInfo{
Master: "MASTER123",
Members: []Member{
{DeviceID: "DEVICE456", IP: "192.168.1.10"},
{DeviceID: "DEVICE789", IP: "192.168.1.11"},
{DeviceID: "DEVICE456", IP: "192.0.2.10"},
{DeviceID: "DEVICE789", IP: "192.0.2.11"},
},
}
@@ -330,8 +330,8 @@ func TestZoneInfo_GetMemberByDeviceID(t *testing.T) {
t.Error("Expected to find member DEVICE456")
}
if member.IP != "192.168.1.10" {
t.Errorf("Expected IP 192.168.1.10, got %s", member.IP)
if member.IP != "192.0.2.10" {
t.Errorf("Expected IP 192.0.2.10, got %s", member.IP)
}
_, found = zi.GetMemberByDeviceID("NONEXISTENT")
@@ -344,21 +344,21 @@ func TestZoneInfo_GetMemberByIP(t *testing.T) {
zi := &ZoneInfo{
Master: "MASTER123",
Members: []Member{
{DeviceID: "DEVICE456", IP: "192.168.1.10"},
{DeviceID: "DEVICE789", IP: "192.168.1.11"},
{DeviceID: "DEVICE456", IP: "192.0.2.10"},
{DeviceID: "DEVICE789", IP: "192.0.2.11"},
},
}
member, found := zi.GetMemberByIP("192.168.1.10")
member, found := zi.GetMemberByIP("192.0.2.10")
if !found {
t.Error("Expected to find member by IP 192.168.1.10")
t.Error("Expected to find member by IP 192.0.2.10")
}
if member.DeviceID != "DEVICE456" {
t.Errorf("Expected device ID DEVICE456, got %s", member.DeviceID)
}
_, found = zi.GetMemberByIP("192.168.1.99")
_, found = zi.GetMemberByIP("192.0.2.99")
if found {
t.Error("Expected not to find member with non-existent IP")
}
@@ -496,8 +496,8 @@ func TestZoneInfo_ToZoneRequest(t *testing.T) {
zi := &ZoneInfo{
Master: "MASTER123",
Members: []Member{
{DeviceID: "DEVICE456", IP: "192.168.1.10"},
{DeviceID: "DEVICE789", IP: "192.168.1.11"},
{DeviceID: "DEVICE456", IP: "192.0.2.10"},
{DeviceID: "DEVICE789", IP: "192.0.2.11"},
},
}
@@ -526,7 +526,7 @@ func TestZoneBuilder(t *testing.T) {
zb := NewZoneBuilder("MASTER123")
zr, err := zb.
WithMember("DEVICE456", "192.168.1.10").
WithMember("DEVICE456", "192.0.2.10").
WithMemberByDeviceID("DEVICE789").
Build()
if err != nil {
@@ -545,8 +545,8 @@ func TestZoneBuilder(t *testing.T) {
t.Errorf("Expected first member DEVICE456, got %s", zr.Members[0].DeviceID)
}
if zr.Members[0].IP != "192.168.1.10" {
t.Errorf("Expected first member IP 192.168.1.10, got %s", zr.Members[0].IP)
if zr.Members[0].IP != "192.0.2.10" {
t.Errorf("Expected first member IP 192.0.2.10, got %s", zr.Members[0].IP)
}
if zr.Members[1].DeviceID != "DEVICE789" {
@@ -672,8 +672,8 @@ func TestZoneCapabilities(t *testing.T) {
func TestZoneXMLMarshaling(t *testing.T) {
t.Run("ZoneInfo XML Unmarshaling", func(t *testing.T) {
xmlData := `<zone master="MASTER123">
<member ipaddress="192.168.1.10">DEVICE456</member>
<member ipaddress="192.168.1.11">DEVICE789</member>
<member ipaddress="192.0.2.10">DEVICE456</member>
<member ipaddress="192.0.2.11">DEVICE789</member>
</zone>`
var zi ZoneInfo
@@ -695,15 +695,15 @@ func TestZoneXMLMarshaling(t *testing.T) {
t.Errorf("Expected first member DEVICE456, got %s", zi.Members[0].DeviceID)
}
if zi.Members[0].IP != "192.168.1.10" {
t.Errorf("Expected first member IP 192.168.1.10, got %s", zi.Members[0].IP)
if zi.Members[0].IP != "192.0.2.10" {
t.Errorf("Expected first member IP 192.0.2.10, got %s", zi.Members[0].IP)
}
})
t.Run("ZoneRequest XML Marshaling", func(t *testing.T) {
zr := NewZoneRequest("MASTER123")
zr.AddMember("DEVICE456", "192.168.1.10")
zr.AddMember("DEVICE789", "192.168.1.11")
zr.AddMember("DEVICE456", "192.0.2.10")
zr.AddMember("DEVICE789", "192.0.2.11")
data, err := xml.MarshalIndent(zr, "", " ")
if err != nil {
@@ -711,8 +711,8 @@ func TestZoneXMLMarshaling(t *testing.T) {
}
expected := `<zone master="MASTER123">
<member ipaddress="192.168.1.10">DEVICE456</member>
<member ipaddress="192.168.1.11">DEVICE789</member>
<member ipaddress="192.0.2.10">DEVICE456</member>
<member ipaddress="192.0.2.11">DEVICE789</member>
</zone>`
if string(data) != expected {
@@ -768,7 +768,7 @@ func TestZoneEdgeCases(t *testing.T) {
for i := 1; i <= 10; i++ {
deviceID := fmt.Sprintf("DEVICE%03d", i)
ip := fmt.Sprintf("192.168.1.%d", i+10)
ip := fmt.Sprintf("192.0.2.%d", i+10)
zr.AddMember(deviceID, ip)
}
@@ -833,7 +833,7 @@ func BenchmarkZoneRequest_Validate(b *testing.B) {
for i := 0; i < 5; i++ {
deviceID := fmt.Sprintf("DEVICE%d", i)
ip := fmt.Sprintf("192.168.1.%d", i+10)
ip := fmt.Sprintf("192.0.2.%d", i+10)
zr.AddMember(deviceID, ip)
}
+5 -5
View File
@@ -138,7 +138,7 @@ func TestCertificateManagerIPAddress(t *testing.T) {
cm := NewCertificateManager(filepath.Join(tempDir, "certs"))
// Test certificate generation with an IP address
domains := []string{"192.168.1.100", "localhost"}
domains := []string{"192.0.2.100", "localhost"}
certPEM, _, err := cm.GenerateCertificate(domains)
if err != nil {
t.Fatalf("Failed to generate certificate: %v", err)
@@ -154,20 +154,20 @@ func TestCertificateManagerIPAddress(t *testing.T) {
// Check IP addresses
foundIP := false
for _, ip := range cert.IPAddresses {
if ip.String() == "192.168.1.100" {
if ip.String() == "192.0.2.100" {
foundIP = true
break
}
}
if !foundIP {
t.Errorf("Expected IP address 192.168.1.100 in IPAddresses, but it was not found")
t.Errorf("Expected IP address 192.0.2.100 in IPAddresses, but it was not found")
}
// Check if it was mistakenly added to DNSNames
for _, dns := range cert.DNSNames {
if dns == "192.168.1.100" {
t.Errorf("IP address 192.168.1.100 should NOT be in DNSNames")
if dns == "192.0.2.100" {
t.Errorf("IP address 192.0.2.100 should NOT be in DNSNames")
}
}
}
+34 -34
View File
@@ -17,7 +17,7 @@ func TestMacAddressCaseSensitivity(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
accountID := "3230304"
accountID := "1000001"
serialNumber := "I6332527703739342000020"
// Test scenarios that could occur in production
@@ -30,43 +30,43 @@ func TestMacAddressCaseSensitivity(t *testing.T) {
}{
{
name: "ExactMatch",
macInDeviceInfo: "A81B6A536A98",
macInRequest: "A81B6A536A98",
macInDeviceInfo: "AABBCCDDEEFF",
macInRequest: "AABBCCDDEEFF",
expectedToWork: true,
description: "Exact case match should work",
},
{
name: "DeviceInfoUpperRequestLower",
macInDeviceInfo: "A81B6A536A98",
macInRequest: "a81b6a536a98",
macInDeviceInfo: "AABBCCDDEEFF",
macInRequest: "aabbccddeeff",
expectedToWork: true,
description: "DeviceInfo has uppercase, request has lowercase (should work with normalization)",
},
{
name: "DeviceInfoLowerRequestUpper",
macInDeviceInfo: "a81b6a536a98",
macInRequest: "A81B6A536A98",
macInDeviceInfo: "aabbccddeeff",
macInRequest: "AABBCCDDEEFF",
expectedToWork: true,
description: "DeviceInfo has lowercase, request has uppercase (should work with normalization)",
},
{
name: "MixedCaseInDeviceInfo",
macInDeviceInfo: "a81B6a536A98",
macInRequest: "A81B6A536A98",
macInDeviceInfo: "aaBBccddEEff",
macInRequest: "AABBCCDDEEFF",
expectedToWork: true,
description: "Mixed case in DeviceInfo vs uppercase request (should work with normalization)",
},
{
name: "WithColonsInDeviceInfo",
macInDeviceInfo: "A8:1B:6A:53:6A:98",
macInRequest: "A81B6A536A98",
macInDeviceInfo: "AA:BB:CC:DD:EE:FF",
macInRequest: "AABBCCDDEEFF",
expectedToWork: true,
description: "DeviceInfo has colons, request without (should work with normalization)",
},
{
name: "WithDashesInDeviceInfo",
macInDeviceInfo: "A8-1B-6A-53-6A-98",
macInRequest: "A81B6A536A98",
macInDeviceInfo: "AA-BB-CC-DD-EE-FF",
macInRequest: "AABBCCDDEEFF",
expectedToWork: true,
description: "DeviceInfo has dashes, request without (should work with normalization)",
},
@@ -94,7 +94,7 @@ func TestMacAddressCaseSensitivity(t *testing.T) {
</components>
<networkInfo type="SCM">
<macAddress>` + tc.macInDeviceInfo + `</macAddress>
<ipAddress>192.168.1.100</ipAddress>
<ipAddress>192.0.2.100</ipAddress>
</networkInfo>
</info>`
if err := os.WriteFile(filepath.Join(deviceDir, constants.DeviceInfoFile), []byte(deviceInfoXML), 0644); err != nil {
@@ -152,8 +152,8 @@ func TestMacAddressCaseSensitivity(t *testing.T) {
// TestProductionScenarioSimulation simulates the exact issue described
func TestProductionScenarioSimulation(t *testing.T) {
// This test specifically simulates the production scenario where:
// Request: GET /streaming/account/3230304/device/A81B6A536A98/presets
// File exists at: /var/lib/soundtouch-service/accounts/3230304/devices/I6332527703739342000020/Presets.xml
// Request: GET /streaming/account/1000001/device/AABBCCDDEEFF/presets
// File exists at: /var/lib/soundtouch-service/accounts/1000001/devices/I6332527703739342000020/Presets.xml
tmpDir, err := os.MkdirTemp("", "production-scenario")
if err != nil {
@@ -161,19 +161,19 @@ func TestProductionScenarioSimulation(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
accountID := "3230304"
accountID := "1000001"
serialNumber := "I6332527703739342000020"
requestMAC := "A81B6A536A98"
requestMAC := "AABBCCDDEEFF"
// Test different MAC address formats that could be in DeviceInfo.xml
possibleMACFormats := []string{
"A81B6A536A98", // Exact match
"a81b6a536a98", // All lowercase
"A81b6a536A98", // Mixed case
"A8:1B:6A:53:6A:98", // With colons
"A8-1B-6A-53-6A-98", // With dashes
"a8:1b:6a:53:6a:98", // Lowercase with colons
"a8-1b-6a-53-6a-98", // Lowercase with dashes
"AABBCCDDEEFF", // Exact match
"aabbccddeeff", // All lowercase
"AAbbccddEEff", // Mixed case
"AA:BB:CC:DD:EE:FF", // With colons
"AA-BB-CC-DD-EE-FF", // With dashes
"aa:bb:cc:dd:ee:ff", // Lowercase with colons
"aa-bb-cc-dd-ee-ff", // Lowercase with dashes
}
t.Logf("Production scenario simulation:")
@@ -203,7 +203,7 @@ func TestProductionScenarioSimulation(t *testing.T) {
</components>
<networkInfo type="SCM">
<macAddress>` + macFormat + `</macAddress>
<ipAddress>192.168.1.100</ipAddress>
<ipAddress>192.0.2.100</ipAddress>
</networkInfo>
</info>`
if err := os.WriteFile(filepath.Join(deviceDir, constants.DeviceInfoFile), []byte(deviceInfoXML), 0644); err != nil {
@@ -259,13 +259,13 @@ func TestNormalizationSuggestion(t *testing.T) {
original string
normalized string
}{
{"A81B6A536A98", "A81B6A536A98"},
{"a81b6a536a98", "A81B6A536A98"},
{"A8:1B:6A:53:6A:98", "A81B6A536A98"},
{"a8:1b:6a:53:6a:98", "A81B6A536A98"},
{"A8-1B-6A-53-6A-98", "A81B6A536A98"},
{"a8-1b-6a-53-6a-98", "A81B6A536A98"},
{"a81B6a536A98", "A81B6A536A98"},
{"AABBCCDDEEFF", "AABBCCDDEEFF"},
{"aabbccddeeff", "AABBCCDDEEFF"},
{"AA:BB:CC:DD:EE:FF", "AABBCCDDEEFF"},
{"aa:bb:cc:dd:ee:ff", "AABBCCDDEEFF"},
{"AA-BB-CC-DD-EE-FF", "AABBCCDDEEFF"},
{"aa-bb-cc-dd-ee-ff", "AABBCCDDEEFF"},
{"aaBBccddEEff", "AABBCCDDEEFF"},
}
t.Log("MAC Address Normalization Test:")
@@ -273,7 +273,7 @@ func TestNormalizationSuggestion(t *testing.T) {
t.Log("")
allNormalizedSame := true
expectedNormalized := "A81B6A536A98"
expectedNormalized := "AABBCCDDEEFF"
for _, tc := range testCases {
normalized := normalizeMAC(tc.original)
+3 -3
View File
@@ -180,7 +180,7 @@ func TestListAllDevices(t *testing.T) {
info := &models.ServiceDeviceInfo{
DeviceID: deviceID,
Name: "Test Speaker",
IPAddress: "192.168.1.100",
IPAddress: "192.0.2.100",
DeviceSerialNumber: deviceID,
ProductCode: "SoundTouch 10",
FirmwareVersion: "1.2.3",
@@ -264,13 +264,13 @@ func TestListAllDevices_MultipleEmptyIDs(t *testing.T) {
info1 := &models.ServiceDeviceInfo{
DeviceID: "",
Name: "Speaker 1",
IPAddress: "192.168.1.1",
IPAddress: "192.0.2.1",
AccountID: account,
}
info2 := &models.ServiceDeviceInfo{
DeviceID: "",
Name: "Speaker 2",
IPAddress: "192.168.1.2",
IPAddress: "192.0.2.2",
AccountID: account,
}
+3 -3
View File
@@ -44,7 +44,7 @@ func TestSaveDeviceInfo_MergesName(t *testing.T) {
AccountID: account,
Name: "",
ProductCode: "SoundTouch 20",
IPAddress: "192.168.1.100",
IPAddress: "192.0.2.100",
}
if err := ds.SaveDeviceInfo(account, device, info2); err != nil {
t.Fatalf("Second SaveDeviceInfo failed: %v", err)
@@ -58,7 +58,7 @@ func TestSaveDeviceInfo_MergesName(t *testing.T) {
if saved2.Name != "Living Room" {
t.Errorf("Expected name 'Living Room' to be preserved, but got '%s'", saved2.Name)
}
if saved2.IPAddress != "192.168.1.100" {
t.Errorf("Expected IPAddress '192.168.1.100', got '%s'", saved2.IPAddress)
if saved2.IPAddress != "192.0.2.100" {
t.Errorf("Expected IPAddress '192.0.2.100', got '%s'", saved2.IPAddress)
}
}
@@ -24,7 +24,7 @@ func TestDNSDiscoveryPersistence(t *testing.T) {
QueryCount: 10,
IsBoseService: true,
IsIntercepted: true,
RemoteAddr: "192.168.1.100",
RemoteAddr: "192.0.2.100",
},
{
Hostname: "google.com",
@@ -33,7 +33,7 @@ func TestDNSDiscoveryPersistence(t *testing.T) {
QueryCount: 5,
IsBoseService: false,
IsIntercepted: false,
RemoteAddr: "192.168.1.101",
RemoteAddr: "192.0.2.101",
},
}
@@ -16,16 +16,16 @@ func TestMacAddressSerialization(t *testing.T) {
defer os.RemoveAll(tempDir)
ds := NewDataStore(tempDir)
account := "3230304"
account := "1000001"
device := "I6332527703739342000020"
macAddress := "A81B6A536A98"
macAddress := "AABBCCDDEEFF"
// Create device info with MAC address
info := &models.ServiceDeviceInfo{
DeviceID: device,
Name: "Test SoundTouch",
ProductCode: "SoundTouch 10",
IPAddress: "192.168.1.100",
IPAddress: "192.0.2.100",
MacAddress: macAddress,
DeviceSerialNumber: device,
ProductSerialNumber: "PROD123456",
@@ -61,8 +61,8 @@ func TestMacAddressSerialization(t *testing.T) {
t.Errorf("DeviceID mismatch. Expected: %s, Got: %s", device, loadedInfo.DeviceID)
}
if loadedInfo.IPAddress != "192.168.1.100" {
t.Errorf("IPAddress mismatch. Expected: 192.168.1.100, Got: %s", loadedInfo.IPAddress)
if loadedInfo.IPAddress != "192.0.2.100" {
t.Errorf("IPAddress mismatch. Expected: 192.0.2.100, Got: %s", loadedInfo.IPAddress)
}
// Initialize datastore to populate MAC mappings
@@ -112,10 +112,10 @@ func TestMacAddressSerializationEdgeCases(t *testing.T) {
macAddress string
expected string
}{
{"uppercase", "A81B6A536A98", "A81B6A536A98"},
{"lowercase", "a81b6a536a98", "a81b6a536a98"},
{"with_colons", "A8:1B:6A:53:6A:98", "A8:1B:6A:53:6A:98"},
{"with_dashes", "A8-1B-6A-53-6A-98", "A8-1B-6A-53-6A-98"},
{"uppercase", "AABBCCDDEEFF", "AABBCCDDEEFF"},
{"lowercase", "aabbccddeeff", "aabbccddeeff"},
{"with_colons", "AA:BB:CC:DD:EE:FF", "AA:BB:CC:DD:EE:FF"},
{"with_dashes", "AA-BB-CC-DD-EE-FF", "AA-BB-CC-DD-EE-FF"},
{"empty", "", ""},
}
@@ -127,7 +127,7 @@ func TestMacAddressSerializationEdgeCases(t *testing.T) {
DeviceID: deviceID,
Name: "Test Device " + tc.name,
ProductCode: "SoundTouch 10",
IPAddress: "192.168.1.100",
IPAddress: "192.0.2.100",
MacAddress: tc.macAddress,
DeviceSerialNumber: deviceID,
}
@@ -159,7 +159,7 @@ func TestExistingDeviceInfoUpdate(t *testing.T) {
defer os.RemoveAll(tempDir)
ds := NewDataStore(tempDir)
account := "3230304"
account := "1000001"
device := "I6332527703739342000020"
// First save without MAC address (simulating old DeviceInfo.xml)
@@ -167,7 +167,7 @@ func TestExistingDeviceInfoUpdate(t *testing.T) {
DeviceID: device,
Name: "Test SoundTouch",
ProductCode: "SoundTouch 10",
IPAddress: "192.168.1.100",
IPAddress: "192.0.2.100",
MacAddress: "", // No MAC address initially
DeviceSerialNumber: device,
}
@@ -188,12 +188,12 @@ func TestExistingDeviceInfoUpdate(t *testing.T) {
}
// Now update with MAC address (simulating discovery update)
macAddress := "A81B6A536A98"
macAddress := "AABBCCDDEEFF"
infoWithMAC := &models.ServiceDeviceInfo{
DeviceID: device,
Name: "Test SoundTouch",
ProductCode: "SoundTouch 10",
IPAddress: "192.168.1.100",
IPAddress: "192.0.2.100",
MacAddress: macAddress,
DeviceSerialNumber: device,
}
@@ -17,7 +17,7 @@ func TestAccountDeviceDir_MACFirstResolution(t *testing.T) {
ds := NewDataStore(tempDir)
accountID := "testaccount"
macAddress := "A81B6A536A98"
macAddress := "AABBCCDDEEFF"
serialNumber := "I6332527703739342000020"
t.Run("NewMACBasedDevice", func(t *testing.T) {
@@ -26,7 +26,7 @@ func TestAccountDeviceDir_MACFirstResolution(t *testing.T) {
DeviceID: macAddress,
AccountID: accountID,
Name: "New MAC Device",
IPAddress: "192.168.1.100",
IPAddress: "192.0.2.100",
MacAddress: macAddress,
DeviceSerialNumber: serialNumber,
ProductCode: "SoundTouch 10 sm2",
@@ -59,7 +59,7 @@ func TestAccountDeviceDir_MACFirstResolution(t *testing.T) {
DeviceID: serialNumber,
AccountID: accountID,
Name: "Legacy Serial Device",
IPAddress: "192.168.1.101",
IPAddress: "192.0.2.101",
MacAddress: macAddress,
DeviceSerialNumber: serialNumber,
ProductCode: "SoundTouch 10",
@@ -153,11 +153,11 @@ func TestAccountDeviceDir_MACFirstResolution(t *testing.T) {
t.Run("MACNormalization", func(t *testing.T) {
// Test different MAC address formats
macFormats := []string{
"A81B6A536A98", // No separators
"A8:1B:6A:53:6A:98", // Colons
"A8-1B-6A-53-6A-98", // Dashes
"a81b6a536a98", // Lowercase
"a8:1b:6a:53:6a:98", // Lowercase with colons
"AABBCCDDEEFF", // No separators
"AA:BB:CC:DD:EE:FF", // Colons
"AA-BB-CC-DD-EE-FF", // Dashes
"aabbccddeeff", // Lowercase
"aa:bb:cc:dd:ee:ff", // Lowercase with colons
}
for _, macFormat := range macFormats {
@@ -275,17 +275,17 @@ func TestMACAddressFormatDetection(t *testing.T) {
expected bool
name string
}{
{"A81B6A536A98", true, "12-char hex"},
{"a81b6a536a98", true, "12-char hex lowercase"},
{"A8:1B:6A:53:6A:98", true, "colon-separated"},
{"A8-1B-6A-53-6A-98", true, "dash-separated"},
{"a8:1b:6a:53:6a:98", true, "colon-separated lowercase"},
{"a8-1b-6a-53-6a-98", true, "dash-separated lowercase"},
{"AABBCCDDEEFF", true, "12-char hex"},
{"aabbccddeeff", true, "12-char hex lowercase"},
{"AA:BB:CC:DD:EE:FF", true, "colon-separated"},
{"AA-BB-CC-DD-EE-FF", true, "dash-separated"},
{"aa:bb:cc:dd:ee:ff", true, "colon-separated lowercase"},
{"aa-bb-cc-dd-ee-ff", true, "dash-separated lowercase"},
{"I6332527703739342000020", false, "device serial"},
{"192.168.1.100", false, "IP address"},
{"192.0.2.100", false, "IP address"},
{"ABCDEFGHIJKL", false, "12-char non-hex"},
{"A8:1B:6A:53:6A", false, "incomplete MAC"},
{"A8:1B:6A:53:6A:98:01", false, "too long MAC"},
{"AA:BB:CC:DD:EE", false, "incomplete MAC"},
{"AA:BB:CC:DD:EE:FF:01", false, "too long MAC"},
{"", false, "empty string"},
}
@@ -308,7 +308,7 @@ func TestAccountDeviceDir_PriorityOrder(t *testing.T) {
ds := NewDataStore(tempDir)
accountID := "prioritytest"
macAddress := "A8:1B:6A:53:6A:98"
macAddress := "AA:BB:CC:DD:EE:FF"
serialNumber := "PRIORITY123456789"
// Create both MAC-based and serial-based devices for the same physical device
@@ -16,9 +16,9 @@ func TestMacMappingDiagnostic(t *testing.T) {
}
defer os.RemoveAll(tmpDir)
accountID := "3230304"
accountID := "1000001"
serialNumber := "I6332527703739342000020"
macAddress := "A81B6A536A98"
macAddress := "AABBCCDDEEFF"
// Create the directory structure as it exists in production
deviceDir := filepath.Join(tmpDir, "accounts", accountID, "devices", serialNumber)
@@ -39,7 +39,7 @@ func TestMacMappingDiagnostic(t *testing.T) {
</components>
<networkInfo type="SCM">
<macAddress>` + macAddress + `</macAddress>
<ipAddress>192.168.1.100</ipAddress>
<ipAddress>192.0.2.100</ipAddress>
</networkInfo>
</info>`
if err := os.WriteFile(filepath.Join(deviceDir, constants.DeviceInfoFile), []byte(deviceInfoXML), 0644); err != nil {
@@ -127,8 +127,8 @@ func TestMacMappingDiagnostic(t *testing.T) {
// Test 5: Check case sensitivity
t.Run("CheckCaseSensitivity", func(t *testing.T) {
lowercaseMAC := "a81b6a536a98"
uppercaseMAC := "A81B6A536A98"
lowercaseMAC := "aabbccddeeff"
uppercaseMAC := "AABBCCDDEEFF"
ds.idMutex.RLock()
_, lowercaseOk := ds.deviceMappings[lowercaseMAC]
@@ -191,11 +191,11 @@ func TestMacMappingWithDifferentFormats(t *testing.T) {
macInRequest string
shouldWork bool
}{
{"ExactMatch", "A81B6A536A98", "A81B6A536A98", true},
{"LowerCase", "A81B6A536A98", "a81b6a536a98", true}, // Should work with normalization
{"UpperCase", "a81b6a536a98", "A81B6A536A98", true}, // Should work with normalization
{"WithColons", "A8:1B:6A:53:6A:98", "A81B6A536A98", true}, // Should work with normalization
{"WithDashes", "A8-1B-6A-53-6A-98", "A81B6A536A98", true}, // Should work with normalization
{"ExactMatch", "AABBCCDDEEFF", "AABBCCDDEEFF", true},
{"LowerCase", "AABBCCDDEEFF", "aabbccddeeff", true}, // Should work with normalization
{"UpperCase", "aabbccddeeff", "AABBCCDDEEFF", true}, // Should work with normalization
{"WithColons", "AA:BB:CC:DD:EE:FF", "AABBCCDDEEFF", true}, // Should work with normalization
{"WithDashes", "AA-BB-CC-DD-EE-FF", "AABBCCDDEEFF", true}, // Should work with normalization
}
for _, tc := range testCases {
@@ -223,7 +223,7 @@ func TestMacMappingWithDifferentFormats(t *testing.T) {
</components>
<networkInfo type="SCM">
<macAddress>` + tc.macInXML + `</macAddress>
<ipAddress>192.168.1.100</ipAddress>
<ipAddress>192.0.2.100</ipAddress>
</networkInfo>
</info>`
if err := os.WriteFile(filepath.Join(deviceDir, constants.DeviceInfoFile), []byte(deviceInfoXML), 0644); err != nil {
+1 -1
View File
@@ -37,7 +37,7 @@ func TestDataStore_MacAddressMapping(t *testing.T) {
</components>
<networkInfo type="SCM">
<macAddress>` + macAddress + `</macAddress>
<ipAddress>192.168.1.10</ipAddress>
<ipAddress>192.0.2.10</ipAddress>
</networkInfo>
</info>`
if err := os.WriteFile(filepath.Join(deviceDir, constants.DeviceInfoFile), []byte(deviceInfoXML), 0644); err != nil {
+19 -19
View File
@@ -28,10 +28,10 @@ func TestUPnPDiscoveryToDatastoreMapping_FullFlow(t *testing.T) {
defer os.RemoveAll(tmpDir)
// Test data matching the user's scenario
accountID := "3230304"
accountID := "1000001"
deviceSerial := "I6332527703739342000020"
deviceMAC := "A81B6A536A98"
deviceName := "Sound Machinechen"
deviceMAC := "AABBCCDDEEFF"
deviceName := "Living Room SoundTouch"
t.Logf("Test scenario:")
t.Logf(" Account: %s", accountID)
@@ -60,7 +60,7 @@ func TestUPnPDiscoveryToDatastoreMapping_FullFlow(t *testing.T) {
</components>
<networkInfo type="SCM">
<macAddress>` + deviceMAC + `</macAddress>
<ipAddress>192.168.1.100</ipAddress>
<ipAddress>192.0.2.100</ipAddress>
</networkInfo>
</info>`
if err := os.WriteFile(filepath.Join(deviceDir, constants.DeviceInfoFile), []byte(deviceInfoXML), 0644); err != nil {
@@ -128,7 +128,7 @@ func TestUPnPDiscoveryToDatastoreMapping_FullFlow(t *testing.T) {
// Simulate UPnP discovery
discoveryService := discovery.NewService(5 * time.Second)
device := &models.DiscoveredDevice{
Host: "192.168.1.100",
Host: "192.0.2.100",
Port: 8091,
Name: "Initial Name",
}
@@ -195,25 +195,25 @@ func TestUPnPDiscoveryToDatastoreMapping_FullFlow(t *testing.T) {
}{
{
name: "ExactMatch",
requestMAC: "A81B6A536A98",
requestMAC: "AABBCCDDEEFF",
shouldWork: true,
description: "Exact MAC match",
},
{
name: "LowercaseMAC",
requestMAC: "a81b6a536a98",
requestMAC: "aabbccddeeff",
shouldWork: true,
description: "Lowercase MAC (should work with normalization)",
},
{
name: "MACWithColons",
requestMAC: "A8:1B:6A:53:6A:98",
requestMAC: "AA:BB:CC:DD:EE:FF",
shouldWork: true,
description: "MAC with colons (should work with normalization)",
},
{
name: "MACWithDashes",
requestMAC: "A8-1B-6A-53-6A-98",
requestMAC: "AA-BB-CC-DD-EE-FF",
shouldWork: true,
description: "MAC with dashes (should work with normalization)",
},
@@ -315,16 +315,16 @@ func TestNormalizationEdgeCases(t *testing.T) {
{"", "", "empty string"},
{"a", "A", "single character"},
{"ab", "AB", "two characters"},
{"A81B6A536A98", "A81B6A536A98", "standard MAC"},
{"a81b6a536a98", "A81B6A536A98", "lowercase MAC"},
{"A8:1B:6A:53:6A:98", "A81B6A536A98", "MAC with colons"},
{"A8-1B-6A-53-6A-98", "A81B6A536A98", "MAC with dashes"},
{"a8:1b:6a:53:6a:98", "A81B6A536A98", "lowercase MAC with colons"},
{"a8-1b-6a-53-6a-98", "A81B6A536A98", "lowercase MAC with dashes"},
{"A8::1B::6A", "A81B6A", "multiple consecutive colons"},
{"A8--1B--6A", "A81B6A", "multiple consecutive dashes"},
{"A8:-1B-:6A", "A81B6A", "mixed separators"},
{" A81B6A536A98 ", "A81B6A536A98", "MAC with spaces (handled by normalization)"},
{"AABBCCDDEEFF", "AABBCCDDEEFF", "standard MAC"},
{"aabbccddeeff", "AABBCCDDEEFF", "lowercase MAC"},
{"AA:BB:CC:DD:EE:FF", "AABBCCDDEEFF", "MAC with colons"},
{"AA-BB-CC-DD-EE-FF", "AABBCCDDEEFF", "MAC with dashes"},
{"aa:bb:cc:dd:ee:ff", "AABBCCDDEEFF", "lowercase MAC with colons"},
{"aa-bb-cc-dd-ee-ff", "AABBCCDDEEFF", "lowercase MAC with dashes"},
{"AA::BB::CC", "AABBCC", "multiple consecutive colons"},
{"AA--BB--CC", "AABBCC", "multiple consecutive dashes"},
{"AA:-BB-:CC", "AABBCC", "mixed separators"},
{" AABBCCDDEEFF ", "AABBCCDDEEFF", "MAC with spaces (handled by normalization)"},
}
for _, tc := range testCases {
+16 -16
View File
@@ -253,7 +253,7 @@ func TestMargeAccountFull(t *testing.T) {
</component>
</components>
<networkInfo type="SCM">
<ipAddress>192.168.1.100</ipAddress>
<ipAddress>192.0.2.100</ipAddress>
</networkInfo>
</info>
`), 0644); err != nil {
@@ -283,7 +283,7 @@ func TestMargeAccountFull(t *testing.T) {
}
// TestMargeAccountFullExcludesEmptyAmazonSource is a regression test for the two-device scenario
// observed in production: device A81B6A536A98 (alphabetically last, used as lastDeviceID)
// observed in production: device AABBCCDDEEFF (alphabetically last, used as lastDeviceID)
// has Sources.xml with 6 sources but no Amazon. The first device has Amazon with empty
// credentials (written before OAuth was implemented). Amazon must NOT appear in /full —
// an empty-credential Amazon causes the speaker's AmazonController to fail JSON parsing.
@@ -296,7 +296,7 @@ func TestMargeAccountFullExcludesEmptyAmazonSource(t *testing.T) {
ds := datastore.NewDataStore(tempDir)
account := "3230304"
account := "1000001"
// First device (alphabetically): has Amazon in Sources.xml
firstDeviceID := "08DF1F0BA325"
@@ -306,7 +306,7 @@ func TestMargeAccountFullExcludesEmptyAmazonSource(t *testing.T) {
}
if err := os.WriteFile(filepath.Join(firstDir, "DeviceInfo.xml"), []byte(`
<info deviceID="08DF1F0BA325">
<name>A Sound Machine</name>
<name>Kitchen SoundTouch</name>
<type>SoundTouch 20 scm</type>
</info>
`), 0644); err != nil {
@@ -322,13 +322,13 @@ func TestMargeAccountFullExcludesEmptyAmazonSource(t *testing.T) {
// Second device (alphabetically last = lastDeviceID): 6 sources but NO Amazon.
// This reproduces the real full.xml returned by the live service.
lastDeviceID := "A81B6A536A98"
lastDeviceID := "AABBCCDDEEFF"
lastDir := filepath.Join(tempDir, "accounts", account, "devices", lastDeviceID)
if err := os.MkdirAll(lastDir, 0755); err != nil {
t.Fatalf("Failed to create last device dir: %v", err)
}
if err := os.WriteFile(filepath.Join(lastDir, "DeviceInfo.xml"), []byte(`
<info deviceID="A81B6A536A98">
<info deviceID="AABBCCDDEEFF">
<name>Another Speaker</name>
<type>SoundTouch 300</type>
</info>
@@ -565,7 +565,7 @@ func TestMargeAccountDevices(t *testing.T) {
deviceInfo := models.ServiceDeviceInfo{
DeviceID: deviceID,
Name: "Test Device",
IPAddress: "192.168.1.100",
IPAddress: "192.0.2.100",
DeviceSerialNumber: "ABCDE12345",
ProductCode: "SoundTouch 20",
ProductSerialNumber: "066802942560222AE",
@@ -599,7 +599,7 @@ func TestMargeAccountDevices(t *testing.T) {
"<devices>",
"<device deviceid=\"DEV1\">",
"<name>Test Device</name>",
"<ipaddress>192.168.1.100</ipaddress>",
"<ipaddress>192.0.2.100</ipaddress>",
"<providerSettings>",
"ELIGIBLE_FOR_TRIAL",
}
@@ -1296,7 +1296,7 @@ func TestMargeAddRemoveDevice(t *testing.T) {
</component>
</components>
<networkInfo type="SCM">
<ipAddress>192.168.1.101</ipAddress>
<ipAddress>192.0.2.101</ipAddress>
</networkInfo>
</device>`
@@ -1358,7 +1358,7 @@ func TestMargePowerOn(t *testing.T) {
})
t.Run("FullBody", func(t *testing.T) {
payload := `<?xml version="1.0" encoding="UTF-8" ?><device-data><device id="001122334455"><serialnumber>I6332527703739342000020</serialnumber><firmware-version>27.0.6.46330</firmware-version><product product_code="SoundTouch 10 sm2" type="5"><serialnumber>069231P63364828AE</serialnumber></product></device><diagnostic-data><device-landscape><rssi>Excellent</rssi><gateway-ip-address>192.168.1.1</gateway-ip-address><macaddresses><macaddress>001122334455</macaddress></macaddresses><ip-address>192.168.1.100</ip-address><network-connection-type>Wireless</network-connection-type></device-landscape></diagnostic-data></device-data>`
payload := `<?xml version="1.0" encoding="UTF-8" ?><device-data><device id="001122334455"><serialnumber>I6332527703739342000020</serialnumber><firmware-version>27.0.6.46330</firmware-version><product product_code="SoundTouch 10 sm2" type="5"><serialnumber>069231P63364828AE</serialnumber></product></device><diagnostic-data><device-landscape><rssi>Excellent</rssi><gateway-ip-address>192.0.2.1</gateway-ip-address><macaddresses><macaddress>001122334455</macaddress></macaddresses><ip-address>192.0.2.100</ip-address><network-connection-type>Wireless</network-connection-type></device-landscape></diagnostic-data></device-data>`
res, err := http.Post(ts.URL+"/marge/streaming/support/power_on", "application/vnd.bose.streaming-v1.2+xml", strings.NewReader(payload))
if err != nil {
t.Fatal(err)
@@ -1386,7 +1386,7 @@ func TestMargePowerOn(t *testing.T) {
firmware := "27.0.6.46330"
productCode := "SoundTouch 10 sm2"
productSerial := "069231P63364828AE"
ipAddress := "192.168.1.100"
ipAddress := "192.0.2.100"
macAddress := "001122334455"
payload := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8" ?>
@@ -1401,7 +1401,7 @@ func TestMargePowerOn(t *testing.T) {
<diagnostic-data>
<device-landscape>
<rssi>Excellent</rssi>
<gateway-ip-address>192.168.1.1</gateway-ip-address>
<gateway-ip-address>192.0.2.1</gateway-ip-address>
<macaddresses>
<macaddress>%s</macaddress>
</macaddresses>
@@ -1528,7 +1528,7 @@ func TestMargeAdvancedFeatures(t *testing.T) {
account := "A123"
deviceId := "587A628A4042"
macAddress := "AABBCCDDEEFF"
ipAddress := "192.168.1.100"
ipAddress := "192.0.2.100"
firmware := "27.0.6"
// Pre-register device
@@ -1722,10 +1722,10 @@ func TestMargeGroupCRUD(t *testing.T) {
<name>Living Room Stereo</name>
<masterDeviceId>` + device1 + `</masterDeviceId>
<roles>
<groupRole><deviceId>` + device1 + `</deviceId><role>LEFT</role><ipAddress>192.168.1.10</ipAddress></groupRole>
<groupRole><deviceId>` + device2 + `</deviceId><role>RIGHT</role><ipAddress>192.168.1.11</ipAddress></groupRole>
<groupRole><deviceId>` + device1 + `</deviceId><role>LEFT</role><ipAddress>192.0.2.10</ipAddress></groupRole>
<groupRole><deviceId>` + device2 + `</deviceId><role>RIGHT</role><ipAddress>192.0.2.11</ipAddress></groupRole>
</roles>
<senderIPAddress>192.168.1.10</senderIPAddress>
<senderIPAddress>192.0.2.10</senderIPAddress>
</group>`
var groupID string
+9 -9
View File
@@ -183,7 +183,7 @@ func TestMigrationAndCA(t *testing.T) {
// Mock HTTPGet to avoid real network timeouts
sm.HTTPGet = func(url string) (*http.Response, error) {
if strings.HasSuffix(url, "/info") {
xml := `<?xml version="1.0" encoding="UTF-8" ?><info deviceID="192.168.1.10"><name>Test Speaker</name><type>SoundTouch 10</type><margeAccountUUID>default</margeAccountUUID></info>`
xml := `<?xml version="1.0" encoding="UTF-8" ?><info deviceID="192.0.2.10"><name>Test Speaker</name><type>SoundTouch 10</type><margeAccountUUID>default</margeAccountUUID></info>`
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader(xml)),
@@ -202,9 +202,9 @@ func TestMigrationAndCA(t *testing.T) {
defer ts.Close()
// Add device to datastore for resolution
_ = ds.SaveDeviceInfo("default", "192.168.1.10", &models.ServiceDeviceInfo{
DeviceID: "192.168.1.10",
IPAddress: "192.168.1.10",
_ = ds.SaveDeviceInfo("default", "192.0.2.10", &models.ServiceDeviceInfo{
DeviceID: "192.0.2.10",
IPAddress: "192.0.2.10",
AccountID: "default",
})
@@ -223,7 +223,7 @@ func TestMigrationAndCA(t *testing.T) {
}
// 2. Test POST /setup/migrate/{deviceIP}?method=hosts
res, err = http.Post(ts.URL+"/setup/migrate/192.168.1.10?method=hosts&target_url=http://192.168.1.100:8000", "application/json", nil)
res, err = http.Post(ts.URL+"/setup/migrate/192.0.2.10?method=hosts&target_url=http://192.0.2.100:8000", "application/json", nil)
if err != nil {
t.Fatal(err)
}
@@ -245,7 +245,7 @@ func TestMigrationAndCA(t *testing.T) {
}
// 3. Test POST /setup/trust-ca/{deviceIP}
res, err = http.Post(ts.URL+"/setup/trust-ca/192.168.1.10", "application/json", nil)
res, err = http.Post(ts.URL+"/setup/trust-ca/192.0.2.10", "application/json", nil)
if err != nil {
t.Fatal(err)
}
@@ -266,7 +266,7 @@ func TestMigrationAndCA(t *testing.T) {
}
// 4. Test POST /setup/reboot/{deviceIP}
res, err = http.Post(ts.URL+"/setup/reboot/192.168.1.10", "application/json", nil)
res, err = http.Post(ts.URL+"/setup/reboot/192.0.2.10", "application/json", nil)
if err != nil {
t.Fatal(err)
}
@@ -287,7 +287,7 @@ func TestMigrationAndCA(t *testing.T) {
}
// 5. Test POST /setup/remove-remote-services/{deviceIP}
res, err = http.Post(ts.URL+"/setup/remove-remote-services/192.168.1.10", "application/json", nil)
res, err = http.Post(ts.URL+"/setup/remove-remote-services/192.0.2.10", "application/json", nil)
if err != nil {
t.Fatal(err)
}
@@ -412,7 +412,7 @@ func (m *mockSSH) Run(command string) (string, error) {
m.runCount++
if m.runCount > 1 {
// Return updated hosts for verification
return "127.0.0.1 localhost\n192.168.1.100\tstreaming.bose.com\n192.168.1.100\tupdates.bose.com\n192.168.1.100\tstats.bose.com\n192.168.1.100\tbmx.bose.com\n192.168.1.100\tcontent.api.bose.io\n192.168.1.100\tevents.api.bosecm.com\n192.168.1.100\tbose-prod.apigee.net\n192.168.1.100\tworldwide.bose.com\n192.168.1.100\tmedia.bose.io\n192.168.1.100\tdownloads.bose.com\n192.168.1.100\tvoice.api.bose.io", nil
return "127.0.0.1 localhost\n192.0.2.100\tstreaming.bose.com\n192.0.2.100\tupdates.bose.com\n192.0.2.100\tstats.bose.com\n192.0.2.100\tbmx.bose.com\n192.0.2.100\tcontent.api.bose.io\n192.0.2.100\tevents.api.bosecm.com\n192.0.2.100\tbose-prod.apigee.net\n192.0.2.100\tworldwide.bose.com\n192.0.2.100\tmedia.bose.io\n192.0.2.100\tdownloads.bose.com\n192.0.2.100\tvoice.api.bose.io", nil
}
return "127.0.0.1 localhost", nil
}
@@ -34,7 +34,7 @@ import (
// and the Bose App showed the rename spinning indefinitely.
//
// The fixture at testdata/issue285/rename_request.xml is the exact
// payload from the log (line 36) — `deviceid="884AEAEEBD27"`,
// payload from the log (line 36) — `deviceid="AABBCCDDEE02"`,
// `<name>Wohnzimmer SB</name>`. The test:
//
// 1. Pre-seeds the datastore with a device record under the
@@ -66,7 +66,7 @@ func TestIssue285_RenamePutAcceptedAndPersisted(t *testing.T) {
const (
accountID = "3981561"
deviceID = "884AEAEEBD27"
deviceID = "AABBCCDDEE02"
oldName = "Wohnzimmer"
newName = "Wohnzimmer SB"
preExistingIP = "192.168.0.109"
@@ -78,7 +78,7 @@ func TestIssue285_RenamePutAcceptedAndPersisted(t *testing.T) {
// data models a long-paired device the user is now renaming —
// CreatedOn must survive the PUT (real Bose preserves it
// across renames; see parity capture at
// data/parity_mismatches/1771797308__streaming_account_3230304_device_A81B6A536A98.json).
// data/parity_mismatches/1771797308__streaming_account_1000001_device_AABBCCDDEEFF.json).
if err := ds.SaveDeviceInfo(accountID, deviceID, &models.ServiceDeviceInfo{
DeviceID: deviceID,
AccountID: accountID,
@@ -228,8 +228,8 @@ func TestIssue285_NewDeviceGetsRemoteAddrAndFreshTimestamps(t *testing.T) {
const (
accountID = "1111111"
deviceID = "A81B6A536A98"
newName = "Sound Machinechen"
deviceID = "AABBCCDDEEFF"
newName = "Living Room SoundTouch"
)
r, _ := setupRouter("http://localhost:8001", ds)
@@ -314,7 +314,7 @@ func TestIssue285_RenamePutRejectsMismatchedDeviceID(t *testing.T) {
t.Cleanup(ts.Close)
const urlDeviceID = "884AEAEEBD27"
const urlDeviceID = "AABBCCDDEE02"
// Body claims a different deviceID than the URL.
body := []byte(`<?xml version="1.0" encoding="UTF-8" ?>` +
@@ -24,7 +24,7 @@ func TestMACBasedDeviceDiscovery_Integration(t *testing.T) {
// Mock device info response (real-world example)
deviceInfoXML := `<info deviceID="001122334455">
<name>Sound Machinechen</name>
<name>Living Room SoundTouch</name>
<type>SoundTouch 10</type>
<margeAccountUUID>1234567</margeAccountUUID>
<components>
@@ -42,11 +42,11 @@ func TestMACBasedDeviceDiscovery_Integration(t *testing.T) {
<margeURL>https://streaming.bose.com</margeURL>
<networkInfo type="SCM">
<macAddress>001122334455</macAddress>
<ipAddress>192.168.1.100</ipAddress>
<ipAddress>192.0.2.100</ipAddress>
</networkInfo>
<networkInfo type="SMSC">
<macAddress>A81B6A849D99</macAddress>
<ipAddress>192.168.1.100</ipAddress>
<macAddress>AABBCCDDEE01</macAddress>
<ipAddress>192.0.2.100</ipAddress>
</networkInfo>
<moduleType>sm2</moduleType>
<variant>rhino</variant>
@@ -127,8 +127,8 @@ func TestMACBasedDeviceDiscovery_Integration(t *testing.T) {
t.Errorf("Expected accountID '%s', got '%s'", expectedAccountID, deviceInfo.AccountID)
}
if deviceInfo.Name != "Sound Machinechen" {
t.Errorf("Expected name 'Sound Machinechen' (from /info), got '%s'", deviceInfo.Name)
if deviceInfo.Name != "Living Room SoundTouch" {
t.Errorf("Expected name 'Living Room SoundTouch' (from /info), got '%s'", deviceInfo.Name)
}
if deviceInfo.ProductCode != "SoundTouch 10 sm2" {
@@ -244,7 +244,7 @@ func TestMACBasedDeviceDiscovery_MigrationScenario(t *testing.T) {
accountID := "1234567"
// 1. Create an existing device entry using IP address (old style)
oldDeviceID := "192.168.1.100"
oldDeviceID := "192.0.2.100"
oldInfo := &models.ServiceDeviceInfo{
DeviceID: oldDeviceID,
AccountID: accountID,
@@ -282,7 +282,7 @@ func TestMACBasedDeviceDiscovery_MigrationScenario(t *testing.T) {
// 2. Mock the same device now providing proper /info response
deviceInfoXML := `<info deviceID="001122334455">
<name>Sound Machinechen</name>
<name>Living Room SoundTouch</name>
<type>SoundTouch 10</type>
<margeAccountUUID>1234567</margeAccountUUID>
<components>
@@ -294,7 +294,7 @@ func TestMACBasedDeviceDiscovery_MigrationScenario(t *testing.T) {
</components>
<networkInfo type="SCM">
<macAddress>001122334455</macAddress>
<ipAddress>192.168.1.100</ipAddress>
<ipAddress>192.0.2.100</ipAddress>
</networkInfo>
<moduleType>sm2</moduleType>
</info>`
@@ -336,8 +336,8 @@ func TestMACBasedDeviceDiscovery_MigrationScenario(t *testing.T) {
t.Errorf("Expected new deviceID '%s', got '%s'", newDeviceID, newInfo.DeviceID)
}
if newInfo.Name != "Sound Machinechen" {
t.Errorf("Expected name from /info 'Sound Machinechen', got '%s'", newInfo.Name)
if newInfo.Name != "Living Room SoundTouch" {
t.Errorf("Expected name from /info 'Living Room SoundTouch', got '%s'", newInfo.Name)
}
t.Logf("\nMigration completed:")
@@ -46,7 +46,7 @@ func TestMacMappingIntegration_HTTPHandler(t *testing.T) {
</components>
<networkInfo type="SCM">
<macAddress>` + macAddress + `</macAddress>
<ipAddress>192.168.1.100</ipAddress>
<ipAddress>192.0.2.100</ipAddress>
</networkInfo>
</info>`
if err := os.WriteFile(filepath.Join(deviceDir, constants.DeviceInfoFile), []byte(deviceInfoXML), 0644); err != nil {
@@ -184,7 +184,7 @@ func TestMacMappingIntegration_HTTPHandler(t *testing.T) {
// Test 4: Case sensitivity test
t.Run("RequestWithLowercaseMAC", func(t *testing.T) {
lowercaseMAC := "a81b6a536a98"
lowercaseMAC := "aabbccddeeff"
requestURL := "/streaming/account/" + accountID + "/device/" + lowercaseMAC + "/presets"
req, err := http.NewRequest("GET", requestURL, nil)
if err != nil {
@@ -287,7 +287,7 @@ func TestMacMappingDebug(t *testing.T) {
</components>
<networkInfo type="SCM">
<macAddress>` + device.mac + `</macAddress>
<ipAddress>192.168.1.100</ipAddress>
<ipAddress>192.0.2.100</ipAddress>
</networkInfo>
</info>`
if err := os.WriteFile(filepath.Join(deviceDir, constants.DeviceInfoFile), []byte(deviceInfoXML), 0644); err != nil {
@@ -28,14 +28,14 @@ func TestTrustedRealIP(t *testing.T) {
{
name: "trusted peer with X-Real-IP is honoured",
remoteAddr: "127.0.0.1:54321",
xRealIP: "192.168.1.10",
wantRemoteAddr: "192.168.1.10",
xRealIP: "192.0.2.10",
wantRemoteAddr: "192.0.2.10",
},
{
name: "trusted peer with X-Forwarded-For is honoured",
remoteAddr: "127.0.0.1:54321",
xForwardedFor: "192.168.1.20, 10.0.0.1",
wantRemoteAddr: "192.168.1.20",
xForwardedFor: "192.0.2.20, 10.0.0.1",
wantRemoteAddr: "192.0.2.20",
},
{
name: "trusted peer with no headers leaves RemoteAddr alone",
@@ -44,15 +44,15 @@ func TestTrustedRealIP(t *testing.T) {
},
{
name: "untrusted peer's X-Real-IP is ignored",
remoteAddr: "192.168.1.99:54321",
remoteAddr: "192.0.2.99:54321",
xRealIP: "1.2.3.4",
wantRemoteAddr: "192.168.1.99:54321",
wantRemoteAddr: "192.0.2.99:54321",
},
{
name: "untrusted peer's X-Forwarded-For is ignored",
remoteAddr: "192.168.1.99:54321",
remoteAddr: "192.0.2.99:54321",
xForwardedFor: "1.2.3.4",
wantRemoteAddr: "192.168.1.99:54321",
wantRemoteAddr: "192.0.2.99:54321",
},
{
name: "trusted peer with garbage X-Real-IP leaves RemoteAddr alone",
+7 -7
View File
@@ -10,19 +10,19 @@ import (
func TestPeerObserver_RegisterSignalForget(t *testing.T) {
o := newPeerObserver()
ch := o.Register("192.168.1.42")
ch := o.Register("192.0.2.42")
if ch == nil {
t.Fatal("Register returned nil channel")
}
first := setup.PeerHit{Path: "/updates/soundtouch", At: time.Now()}
if !o.Signal("192.168.1.42", first) {
if !o.Signal("192.0.2.42", first) {
t.Error("Signal returned false for registered IP")
}
// Second signal while the buffer is still full (no reader yet) drops
// silently and returns false — only the first hit per window matters.
if o.Signal("192.168.1.42", setup.PeerHit{Path: "/streaming/x"}) {
if o.Signal("192.0.2.42", setup.PeerHit{Path: "/streaming/x"}) {
t.Error("second Signal returned true; expected false (buffer full, undrained)")
}
@@ -35,10 +35,10 @@ func TestPeerObserver_RegisterSignalForget(t *testing.T) {
t.Error("Signal did not deliver hit to channel")
}
o.Forget("192.168.1.42")
o.Forget("192.0.2.42")
// After Forget, Signal returns false.
if o.Signal("192.168.1.42", first) {
if o.Signal("192.0.2.42", first) {
t.Error("Signal returned true after Forget")
}
}
@@ -52,12 +52,12 @@ func TestPeerObserver_UnknownIP(t *testing.T) {
func TestPeerObserver_SignalIsNonBlocking(t *testing.T) {
o := newPeerObserver()
o.Register("192.168.1.42") // never drain
o.Register("192.0.2.42") // never drain
done := make(chan struct{})
go func() {
for i := 0; i < 100; i++ {
o.Signal("192.168.1.42", setup.PeerHit{Path: "/x"})
o.Signal("192.0.2.42", setup.PeerHit{Path: "/x"})
}
close(done)
}()
+2 -2
View File
@@ -104,7 +104,7 @@ func TestFormatPreflightGuidance_BothFailMentionsRedirectPort(t *testing.T) {
res := Probe443Result{
Localhost: ProbeOutcome{Error: "connection refused"},
LAN: ProbeOutcome{Error: "connection refused"},
LANHost: "192.168.1.151",
LANHost: "192.0.2.151",
}
out := FormatPreflightGuidance(8443, res)
@@ -112,7 +112,7 @@ func TestFormatPreflightGuidance_BothFailMentionsRedirectPort(t *testing.T) {
t.Errorf("guidance must reference configured listener port for iptables, got: %s", out)
}
if !strings.Contains(out, "192.168.1.151:443") {
if !strings.Contains(out, "192.0.2.151:443") {
t.Errorf("guidance must mention probed LAN host, got: %s", out)
}
+2 -2
View File
@@ -19,7 +19,7 @@ func TestMergeOverlappingDevices(t *testing.T) {
s := NewServer(ds, nil, "http://localhost", false, false, false)
// Case 1: IP-only entry and Serial-based entry for the same IP
ip := "192.168.1.100"
ip := "192.0.2.100"
serial := "SERIAL123"
// 1. Save IP-based entry
@@ -76,7 +76,7 @@ func TestFindExistingDeviceID(t *testing.T) {
ds := datastore.NewDataStore(tempDir)
s := NewServer(ds, nil, "http://localhost", false, false, false)
ip := "192.168.1.101"
ip := "192.0.2.101"
serial := "SERIAL456"
// Save IP-based
+1 -1
View File
@@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8" ?><device deviceid="884AEAEEBD27"><name>Wohnzimmer SB</name><macaddress>884AEAEEBD27</macaddress></device>
<?xml version="1.0" encoding="UTF-8" ?><device deviceid="AABBCCDDEE02"><name>Wohnzimmer SB</name><macaddress>AABBCCDDEE02</macaddress></device>
+8 -8
View File
@@ -94,12 +94,12 @@ func TestAccountFullToXML_Structure(t *testing.T) {
// 1. Setup Device Info with Components
info := &models.ServiceDeviceInfo{
DeviceID: device,
Name: "A Sound Machine",
Name: "Kitchen SoundTouch",
ProductCode: "SoundTouch 20",
DeviceSerialNumber: device,
ProductSerialNumber: "066802942560222AE",
FirmwareVersion: "27.0.6.46330.5043500",
IPAddress: "192.168.178.28",
IPAddress: "192.0.2.28",
Components: []models.ServiceComponent{
{
Category: "SMSC",
@@ -186,8 +186,8 @@ func TestAccountFullToXML_Structure(t *testing.T) {
if !strings.Contains(xmlStr, `<device deviceid="08DF1F0BA325">`) {
t.Errorf("Expected device attribute deviceid, got %s", xmlStr)
}
if !strings.Contains(xmlStr, `<name>A Sound Machine</name>`) {
t.Errorf("Expected <name>A Sound Machine</name> under device, got %s", xmlStr)
if !strings.Contains(xmlStr, `<name>Kitchen SoundTouch</name>`) {
t.Errorf("Expected <name>Kitchen SoundTouch</name> under device, got %s", xmlStr)
}
if !strings.Contains(xmlStr, `<serialNumber>08DF1F0BA325</serialNumber>`) {
t.Errorf("Expected <serialNumber>08DF1F0BA325</serialNumber> under device, got %s", xmlStr)
@@ -715,7 +715,7 @@ func TestAccountFullToXML_WithBackupStructure(t *testing.T) {
deviceInfoXML := `<?xml version="1.0" encoding="UTF-8"?>
<info deviceID="001122334455">
<name>Sound Machinechen</name>
<name>Living Room SoundTouch</name>
<type>SoundTouch</type>
<moduleType>10 sm2</moduleType>
<components>
@@ -726,7 +726,7 @@ func TestAccountFullToXML_WithBackupStructure(t *testing.T) {
</component>
</components>
<networkInfo type="SCM">
<ipAddress>192.168.178.35</ipAddress>
<ipAddress>192.0.2.35</ipAddress>
<macAddress>001122334455</macAddress>
</networkInfo>
<discoveryMethod>sync_full</discoveryMethod>
@@ -744,8 +744,8 @@ func TestAccountFullToXML_WithBackupStructure(t *testing.T) {
xmlStr := string(fullXML)
// Verify Name is present
if !strings.Contains(xmlStr, `<name>Sound Machinechen</name>`) {
t.Errorf("Expected <name>Sound Machinechen</name> under device, got %s", xmlStr)
if !strings.Contains(xmlStr, `<name>Living Room SoundTouch</name>`) {
t.Errorf("Expected <name>Living Room SoundTouch</name> under device, got %s", xmlStr)
}
// 2. Verify ButtonNumber and ContentItemType mapping
+6 -6
View File
@@ -103,7 +103,7 @@ func TestReproduceMissingName(t *testing.T) {
t.Fatal(err)
}
dev1Info := `<info deviceID="08DF1F0BA325">
<name>A Sound Machine</name>
<name>Kitchen SoundTouch</name>
<type>SoundTouch</type>
<moduleType>20</moduleType>
<components>
@@ -128,7 +128,7 @@ func TestReproduceMissingName(t *testing.T) {
}
dev2Info := `<?xml version="1.0" encoding="UTF-8"?>
<info deviceID="001122334455">
<name>Sound Machinechen</name>
<name>Living Room SoundTouch</name>
<type>SoundTouch</type>
<moduleType>10 sm2</moduleType>
<components>
@@ -143,7 +143,7 @@ func TestReproduceMissingName(t *testing.T) {
</component>
</components>
<networkInfo type="SCM">
<ipAddress>192.168.178.35</ipAddress>
<ipAddress>192.0.2.35</ipAddress>
<macAddress>001122334455</macAddress>
</networkInfo>
<discoveryMethod>sync_full</discoveryMethod>
@@ -190,7 +190,7 @@ func TestReproduceMissingName(t *testing.T) {
globalDevDir := filepath.Join("st-go", "data", "accounts", "default", "devices", "001122334455")
os.MkdirAll(globalDevDir, 0755)
defer os.RemoveAll("st-go")
globalDevInfo := `<info deviceID="001122334455"><name>Sound Machinechen</name><type>SoundTouch</type><moduleType>10 sm2</moduleType></info>`
globalDevInfo := `<info deviceID="001122334455"><name>Living Room SoundTouch</name><type>SoundTouch</type><moduleType>10 sm2</moduleType></info>`
os.WriteFile(filepath.Join(globalDevDir, "DeviceInfo.xml"), []byte(globalDevInfo), 0644)
// Create a directory in 'default' with EMPTY name (the one that GetDeviceInfo will pick up)
@@ -210,8 +210,8 @@ func TestReproduceMissingName(t *testing.T) {
t.Fatal(err)
}
if info.Name != "Sound Machinechen" {
t.Errorf("Expected name 'Sound Machinechen' to be preserved, got '%s'", info.Name)
if info.Name != "Living Room SoundTouch" {
t.Errorf("Expected name 'Living Room SoundTouch' to be preserved, got '%s'", info.Name)
}
// Re-generate XML to see if it now uses the preserved name
+1 -1
View File
@@ -26,7 +26,7 @@ func TestSyncFromAccountFull(t *testing.T) {
<devices>
<device deviceid="DEVICE_ABC">
<name>Living Room</name>
<ipaddress>192.168.1.10</ipaddress>
<ipaddress>192.0.2.10</ipaddress>
<serialNumber>ABC123XYZ</serialNumber>
<firmwareVersion>27.0.6</firmwareVersion>
<attachedProduct product_code="ST10">
+1 -1
View File
@@ -34,7 +34,7 @@ func TestPathPatterns_Sanitize(t *testing.T) {
segment string
wantRepl string
}{
{"192.168.1.100", "{ip}"},
{"192.0.2.100", "{ip}"},
{"1234567", "{accountId}"},
{"12345", "{accountId}"},
{"12345678-1234-5678-9012-123456789012", "{uuid}"},
+6 -6
View File
@@ -52,7 +52,7 @@ func TestRecorder_Record_Structure(t *testing.T) {
{
name: "path_with_ip",
category: "self",
path: "/setup/info/192.168.1.100",
path: "/setup/info/192.0.2.100",
expected: "setup/info/{ip}",
},
{
@@ -131,11 +131,11 @@ func TestRecorder_Record_Sanitization(t *testing.T) {
req := &http.Request{
Method: "GET",
URL: &url.URL{
Path: "/info/192.168.1.100/A81B6A536A98",
Path: "/info/192.0.2.100/AABBCCDDEEFF",
},
Header: make(http.Header),
}
req.Header.Set("X-Device", "A81B6A536A98")
req.Header.Set("X-Device", "AABBCCDDEEFF")
err = r.Record("self", req, nil)
if err != nil {
@@ -331,7 +331,7 @@ func TestRecorder_EnvFile(t *testing.T) {
req := &http.Request{
Method: "GET",
URL: &url.URL{
Path: "/info/192.168.1.100",
Path: "/info/192.0.2.100",
},
Header: make(http.Header),
}
@@ -352,8 +352,8 @@ func TestRecorder_EnvFile(t *testing.T) {
t.Fatalf("Failed to unmarshal env file: %v", err)
}
if content["session"]["ip"] != "192.168.1.100" {
t.Errorf("Expected ip to be 192.168.1.100, got %s", content["session"]["ip"])
if content["session"]["ip"] != "192.0.2.100" {
t.Errorf("Expected ip to be 192.0.2.100, got %s", content["session"]["ip"])
}
}
+34 -34
View File
@@ -7,10 +7,10 @@ import (
func TestDeviceInfoXML_RealWorldParsing(t *testing.T) {
// Real XML response from a SoundTouch device's /info endpoint
xmlData := `<info deviceID="A81B6A536A98">
<name>Sound Machinechen</name>
xmlData := `<info deviceID="AABBCCDDEEFF">
<name>Living Room SoundTouch</name>
<type>SoundTouch 10</type>
<margeAccountUUID>3230304</margeAccountUUID>
<margeAccountUUID>1000001</margeAccountUUID>
<components>
<component>
<componentCategory>SCM</componentCategory>
@@ -25,12 +25,12 @@ func TestDeviceInfoXML_RealWorldParsing(t *testing.T) {
</components>
<margeURL>https://streaming.bose.com</margeURL>
<networkInfo type="SCM">
<macAddress>A81B6A536A98</macAddress>
<ipAddress>192.168.1.100</ipAddress>
<macAddress>AABBCCDDEEFF</macAddress>
<ipAddress>192.0.2.100</ipAddress>
</networkInfo>
<networkInfo type="SMSC">
<macAddress>A81B6A849D99</macAddress>
<ipAddress>192.168.1.100</ipAddress>
<macAddress>AABBCCDDEE01</macAddress>
<ipAddress>192.0.2.100</ipAddress>
</networkInfo>
<moduleType>sm2</moduleType>
<variant>rhino</variant>
@@ -48,12 +48,12 @@ func TestDeviceInfoXML_RealWorldParsing(t *testing.T) {
}
// Verify basic fields
if infoXML.DeviceID != "A81B6A536A98" {
t.Errorf("Expected deviceID 'A81B6A536A98', got '%s'", infoXML.DeviceID)
if infoXML.DeviceID != "AABBCCDDEEFF" {
t.Errorf("Expected deviceID 'AABBCCDDEEFF', got '%s'", infoXML.DeviceID)
}
if infoXML.Name != "Sound Machinechen" {
t.Errorf("Expected name 'Sound Machinechen', got '%s'", infoXML.Name)
if infoXML.Name != "Living Room SoundTouch" {
t.Errorf("Expected name 'Living Room SoundTouch', got '%s'", infoXML.Name)
}
if infoXML.Type != "SoundTouch 10" {
@@ -64,8 +64,8 @@ func TestDeviceInfoXML_RealWorldParsing(t *testing.T) {
t.Errorf("Expected moduleType 'sm2', got '%s'", infoXML.ModuleType)
}
if infoXML.MargeAccountUUID != "3230304" {
t.Errorf("Expected margeAccountUUID '3230304', got '%s'", infoXML.MargeAccountUUID)
if infoXML.MargeAccountUUID != "1000001" {
t.Errorf("Expected margeAccountUUID '1000001', got '%s'", infoXML.MargeAccountUUID)
}
if infoXML.MargeURL != "https://streaming.bose.com" {
@@ -132,19 +132,19 @@ func TestDeviceInfoXML_RealWorldParsing(t *testing.T) {
switch net.Type {
case "SCM":
scmNetworkFound = true
if net.MacAddress != "A81B6A536A98" {
t.Errorf("Expected SCM MAC 'A81B6A536A98', got '%s'", net.MacAddress)
if net.MacAddress != "AABBCCDDEEFF" {
t.Errorf("Expected SCM MAC 'AABBCCDDEEFF', got '%s'", net.MacAddress)
}
if net.IPAddress != "192.168.1.100" {
t.Errorf("Expected SCM IP '192.168.1.100', got '%s'", net.IPAddress)
if net.IPAddress != "192.0.2.100" {
t.Errorf("Expected SCM IP '192.0.2.100', got '%s'", net.IPAddress)
}
case "SMSC":
smscNetworkFound = true
if net.MacAddress != "A81B6A849D99" {
t.Errorf("Expected SMSC MAC 'A81B6A849D99', got '%s'", net.MacAddress)
if net.MacAddress != "AABBCCDDEE01" {
t.Errorf("Expected SMSC MAC 'AABBCCDDEE01', got '%s'", net.MacAddress)
}
if net.IPAddress != "192.168.1.100" {
t.Errorf("Expected SMSC IP '192.168.1.100', got '%s'", net.IPAddress)
if net.IPAddress != "192.0.2.100" {
t.Errorf("Expected SMSC IP '192.0.2.100', got '%s'", net.IPAddress)
}
}
}
@@ -158,8 +158,8 @@ func TestDeviceInfoXML_RealWorldParsing(t *testing.T) {
// Test the GetPrimaryMacAddress method
primaryMAC := infoXML.GetPrimaryMacAddress()
if primaryMAC != "A81B6A536A98" {
t.Errorf("Expected primary MAC 'A81B6A536A98', got '%s'", primaryMAC)
if primaryMAC != "AABBCCDDEEFF" {
t.Errorf("Expected primary MAC 'AABBCCDDEEFF', got '%s'", primaryMAC)
}
t.Logf("✅ Successfully parsed real device info XML")
@@ -194,10 +194,10 @@ func TestDeviceInfoXML_GetPrimaryMacAddress_EdgeCases(t *testing.T) {
MacAddress string
IPAddress string
}{
{"SCM", "A81B6A536A98", "192.168.1.1"},
{"SMSC", "A81B6A849D99", "192.168.1.1"},
{"SCM", "AABBCCDDEEFF", "192.0.2.1"},
{"SMSC", "AABBCCDDEE01", "192.0.2.1"},
},
expected: "A81B6A536A98",
expected: "AABBCCDDEEFF",
},
{
name: "scm_second",
@@ -206,10 +206,10 @@ func TestDeviceInfoXML_GetPrimaryMacAddress_EdgeCases(t *testing.T) {
MacAddress string
IPAddress string
}{
{"SMSC", "A81B6A849D99", "192.168.1.1"},
{"SCM", "A81B6A536A98", "192.168.1.1"},
{"SMSC", "AABBCCDDEE01", "192.0.2.1"},
{"SCM", "AABBCCDDEEFF", "192.0.2.1"},
},
expected: "A81B6A536A98",
expected: "AABBCCDDEEFF",
},
{
name: "no_scm",
@@ -218,8 +218,8 @@ func TestDeviceInfoXML_GetPrimaryMacAddress_EdgeCases(t *testing.T) {
MacAddress string
IPAddress string
}{
{"SMSC", "A81B6A849D99", "192.168.1.1"},
{"OTHER", "A81B6A849D88", "192.168.1.1"},
{"SMSC", "AABBCCDDEE01", "192.0.2.1"},
{"OTHER", "AABBCCDDEE03", "192.0.2.1"},
},
expected: "",
},
@@ -230,8 +230,8 @@ func TestDeviceInfoXML_GetPrimaryMacAddress_EdgeCases(t *testing.T) {
MacAddress string
IPAddress string
}{
{"SCM", "", "192.168.1.1"},
{"SMSC", "A81B6A849D99", "192.168.1.1"},
{"SCM", "", "192.0.2.1"},
{"SMSC", "AABBCCDDEE01", "192.0.2.1"},
},
expected: "",
},
@@ -261,7 +261,7 @@ func TestDeviceInfoXML_GetPrimaryMacAddress_EdgeCases(t *testing.T) {
}
func TestDeviceInfoXML_ComponentParsing(t *testing.T) {
xmlData := `<info deviceID="A81B6A536A98">
xmlData := `<info deviceID="AABBCCDDEEFF">
<name>Test Device</name>
<type>SoundTouch 10</type>
<components>
+5 -5
View File
@@ -56,7 +56,7 @@ func TestInspect_HappyPath(t *testing.T) {
</info>`,
"/networkInfo": `<networkInfo wifiProfileCount="1">
<interfaces>
<interface type="WIFI_INTERFACE" name="wlan0" macAddress="aa:bb:cc:dd:ee:ff" ipAddress="192.168.1.42" ssid="MyHomeNetwork" frequencyKHz="2452000" state="NETWORK_WIFI_CONNECTED" signal="GOOD_SIGNAL" mode="STATION"/>
<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>`,
@@ -66,7 +66,7 @@ func TestInspect_HappyPath(t *testing.T) {
m := &Manager{HTTPGet: f.get}
r := m.Inspect("192.168.1.42", InspectOptions{})
r := m.Inspect("192.0.2.42", InspectOptions{})
if r.InfoErr != nil {
t.Errorf("InfoErr = %v, want nil", r.InfoErr)
@@ -119,7 +119,7 @@ func TestInspect_PartialFailureRecordsPerSectionErrors(t *testing.T) {
m := &Manager{HTTPGet: f.get}
r := m.Inspect("192.168.1.42", InspectOptions{})
r := m.Inspect("192.0.2.42", InspectOptions{})
if r.InfoErr != nil {
t.Errorf("InfoErr = %v, want nil", r.InfoErr)
@@ -152,7 +152,7 @@ func TestInspect_TelnetRuntimeURLs(t *testing.T) {
NewTelnet: func(string) TelnetClient { return tn },
}
r := m.Inspect("192.168.1.42", InspectOptions{IncludeTelnet: true})
r := m.Inspect("192.0.2.42", InspectOptions{IncludeTelnet: true})
if r.RuntimeErr != nil {
t.Errorf("RuntimeErr = %v, want nil", r.RuntimeErr)
@@ -172,7 +172,7 @@ func TestInspect_TelnetSkippedWhenOptionDisabled(t *testing.T) {
m := &Manager{HTTPGet: f.get}
r := m.Inspect("192.168.1.42", InspectOptions{IncludeTelnet: false})
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",
@@ -108,7 +108,7 @@ func TestGetMigrationSummary_IsPairedFromLiveInfo(t *testing.T) {
t.Run("with margeAccountUUID", func(t *testing.T) {
m, host, cleanup := telnetSummaryEnvWithInfo(t, nil, ft,
`<info deviceID="123"><name>Test</name><margeAccountUUID>3230304</margeAccountUUID></info>`,
`<info deviceID="123"><name>Test</name><margeAccountUUID>1000001</margeAccountUUID></info>`,
)
defer cleanup()
@@ -121,8 +121,8 @@ func TestGetMigrationSummary_IsPairedFromLiveInfo(t *testing.T) {
t.Errorf("IsPaired = false, want true (margeAccountUUID present in :8090/info)")
}
if summary.AccountID != "3230304" {
t.Errorf("AccountID = %q, want 3230304 (live info should populate)", summary.AccountID)
if summary.AccountID != "1000001" {
t.Errorf("AccountID = %q, want 1000001 (live info should populate)", summary.AccountID)
}
})
+6 -6
View File
@@ -68,10 +68,10 @@ func TestRunPeerReachabilityProbe_HappyPath(t *testing.T) {
// On nudge, simulate the device fanning out to /updates/soundtouch
// which the middleware would signal as a hit on this IP.
m := peerProbeManager(func() {
obs.signal("192.168.1.42", PeerHit{Path: "/updates/soundtouch", At: time.Now()})
obs.signal("192.0.2.42", PeerHit{Path: "/updates/soundtouch", At: time.Now()})
})
result, err := m.RunPeerReachabilityProbe("192.168.1.42", obs, 2*time.Second)
result, err := m.RunPeerReachabilityProbe("192.0.2.42", obs, 2*time.Second)
if err != nil {
t.Fatalf("RunPeerReachabilityProbe error: %v", err)
}
@@ -81,7 +81,7 @@ func TestRunPeerReachabilityProbe_HappyPath(t *testing.T) {
if result.ObservedPath != "/updates/soundtouch" {
t.Errorf("ObservedPath = %q, want %q", result.ObservedPath, "/updates/soundtouch")
}
if len(obs.forgotten) != 1 || obs.forgotten[0] != "192.168.1.42" {
if len(obs.forgotten) != 1 || obs.forgotten[0] != "192.0.2.42" {
t.Errorf("Forget not called for IP: forgotten = %v", obs.forgotten)
}
}
@@ -91,7 +91,7 @@ func TestRunPeerReachabilityProbe_Timeout(t *testing.T) {
m := peerProbeManager(nil) // nudge fires but device never responds
start := time.Now()
result, err := m.RunPeerReachabilityProbe("192.168.1.42", obs, 200*time.Millisecond)
result, err := m.RunPeerReachabilityProbe("192.0.2.42", obs, 200*time.Millisecond)
elapsed := time.Since(start)
if err != nil {
@@ -110,7 +110,7 @@ func TestRunPeerReachabilityProbe_Timeout(t *testing.T) {
func TestRunPeerReachabilityProbe_NilObserver(t *testing.T) {
m := peerProbeManager(nil)
_, err := m.RunPeerReachabilityProbe("192.168.1.42", nil, time.Second)
_, err := m.RunPeerReachabilityProbe("192.0.2.42", nil, time.Second)
if err == nil {
t.Error("expected error for nil observer, got nil")
}
@@ -132,7 +132,7 @@ func TestRunPeerReachabilityProbe_NilHTTPGetTimesOut(t *testing.T) {
m := &Manager{} // HTTPGet nil
obs := newFakePeerObserver()
result, err := m.RunPeerReachabilityProbe("192.168.1.42", obs, 100*time.Millisecond)
result, err := m.RunPeerReachabilityProbe("192.0.2.42", obs, 100*time.Millisecond)
if err != nil {
t.Fatalf("error: %v", err)
}
+53 -53
View File
@@ -144,7 +144,7 @@ func TestMigrateViaHosts(t *testing.T) {
t.Fatalf("Failed to ensure CA: %v", err)
}
m := NewManager("http://192.168.1.100:8000", nil, cm)
m := NewManager("http://192.0.2.100:8000", nil, cm)
runCalls := []string{}
m.NewSSH = func(host string) SSHClient {
@@ -154,7 +154,7 @@ func TestMigrateViaHosts(t *testing.T) {
if command == "cat /etc/hosts" {
// Handle both initial read and verification read
if len(runCalls) > 2 { // Rough heuristic: verification happens after upload
return "192.168.1.100\tstreaming.bose.com\n192.168.1.100\tupdates.bose.com\n192.168.1.100\tstats.bose.com\n192.168.1.100\tbmx.bose.com\n192.168.1.100\tcontent.api.bose.io\n192.168.1.100\tevents.api.bosecm.com\n192.168.1.100\tbose-prod.apigee.net\n192.168.1.100\tworldwide.bose.com\n192.168.1.100\tmedia.bose.io\n192.168.1.100\tdownloads.bose.com\n192.168.1.100\tvoice.api.bose.io", nil
return "192.0.2.100\tstreaming.bose.com\n192.0.2.100\tupdates.bose.com\n192.0.2.100\tstats.bose.com\n192.0.2.100\tbmx.bose.com\n192.0.2.100\tcontent.api.bose.io\n192.0.2.100\tevents.api.bosecm.com\n192.0.2.100\tbose-prod.apigee.net\n192.0.2.100\tworldwide.bose.com\n192.0.2.100\tmedia.bose.io\n192.0.2.100\tdownloads.bose.com\n192.0.2.100\tvoice.api.bose.io", nil
}
return "127.0.0.1 localhost", nil
}
@@ -168,7 +168,7 @@ func TestMigrateViaHosts(t *testing.T) {
},
uploadContentFunc: func(content []byte, remotePath string) error {
if remotePath == "/etc/hosts" {
if !strings.Contains(string(content), "192.168.1.100\tstreaming.bose.com") {
if !strings.Contains(string(content), "192.0.2.100\tstreaming.bose.com") {
t.Errorf("Expected hosts content to contain redirect, got %s", string(content))
}
}
@@ -177,7 +177,7 @@ func TestMigrateViaHosts(t *testing.T) {
}
}
_, err = m.migrateViaHosts("192.168.1.10", "http://192.168.1.100:8000")
_, err = m.migrateViaHosts("192.0.2.10", "http://192.0.2.100:8000")
if err != nil {
t.Fatalf("migrateViaHosts failed: %v", err)
}
@@ -223,7 +223,7 @@ func TestMigrateViaHosts_UpdateExisting(t *testing.T) {
cm := certmanager.NewCertificateManager(filepath.Join(tempDir, "certs"))
_ = cm.EnsureCA()
m := NewManager("http://192.168.1.100:8000", nil, cm)
m := NewManager("http://192.0.2.100:8000", nil, cm)
m.NewSSH = func(host string) SSHClient {
runCount := 0
@@ -232,7 +232,7 @@ func TestMigrateViaHosts_UpdateExisting(t *testing.T) {
runCount++
if command == "cat /etc/hosts" {
if runCount > 1 {
return "127.0.0.1 localhost\n192.168.1.100\tstreaming.bose.com\n192.168.1.100\tupdates.bose.com\n192.168.1.100\tstats.bose.com\n192.168.1.100\tbmx.bose.com\n192.168.1.100\tcontent.api.bose.io\n192.168.1.100\tevents.api.bosecm.com\n192.168.1.100\tbose-prod.apigee.net\n192.168.1.100\tworldwide.bose.com\n192.168.1.100\tmedia.bose.io\n192.168.1.100\tdownloads.bose.com\n192.168.1.100\tvoice.api.bose.io", nil
return "127.0.0.1 localhost\n192.0.2.100\tstreaming.bose.com\n192.0.2.100\tupdates.bose.com\n192.0.2.100\tstats.bose.com\n192.0.2.100\tbmx.bose.com\n192.0.2.100\tcontent.api.bose.io\n192.0.2.100\tevents.api.bosecm.com\n192.0.2.100\tbose-prod.apigee.net\n192.0.2.100\tworldwide.bose.com\n192.0.2.100\tmedia.bose.io\n192.0.2.100\tdownloads.bose.com\n192.0.2.100\tvoice.api.bose.io", nil
}
return "127.0.0.1 localhost\n1.2.3.4\tstreaming.bose.com\n1.2.3.4\tupdates.bose.com", nil
}
@@ -247,13 +247,13 @@ func TestMigrateViaHosts_UpdateExisting(t *testing.T) {
uploadContentFunc: func(content []byte, remotePath string) error {
if remotePath == "/etc/hosts" {
c := string(content)
if !strings.Contains(c, "192.168.1.100\tstreaming.bose.com") {
if !strings.Contains(c, "192.0.2.100\tstreaming.bose.com") {
t.Errorf("Expected updated IP for streaming.bose.com, got:\n%s", c)
}
if !strings.Contains(c, "192.168.1.100\tupdates.bose.com") {
if !strings.Contains(c, "192.0.2.100\tupdates.bose.com") {
t.Errorf("Expected updated IP for updates.bose.com, got:\n%s", c)
}
if !strings.Contains(c, "192.168.1.100\tevents.api.bosecm.com") {
if !strings.Contains(c, "192.0.2.100\tevents.api.bosecm.com") {
t.Errorf("Expected new domain events.api.bosecm.com, got:\n%s", c)
}
// Ensure no duplicates
@@ -266,7 +266,7 @@ func TestMigrateViaHosts_UpdateExisting(t *testing.T) {
}
}
_, err = m.migrateViaHosts("192.168.1.10", "http://192.168.1.100:8000")
_, err = m.migrateViaHosts("192.0.2.10", "http://192.0.2.100:8000")
if err != nil {
t.Fatalf("migrateViaHosts failed: %v", err)
}
@@ -407,7 +407,7 @@ func TestCheckCACertTrusted(t *testing.T) {
}
summary := &MigrationSummary{}
m.checkCACertTrusted(summary, "192.168.1.10")
m.checkCACertTrusted(summary, "192.0.2.10")
if !summary.CACertTrusted {
t.Errorf("Expected CACertTrusted to be true when label is found")
}
@@ -429,7 +429,7 @@ func TestCheckCACertTrusted(t *testing.T) {
}
summary = &MigrationSummary{}
m.checkCACertTrusted(summary, "192.168.1.10")
m.checkCACertTrusted(summary, "192.0.2.10")
if !summary.CACertTrusted {
t.Errorf("Expected CACertTrusted to be true when cert data is found")
}
@@ -447,7 +447,7 @@ func TestCheckCACertTrusted(t *testing.T) {
}
summary = &MigrationSummary{}
m.checkCACertTrusted(summary, "192.168.1.10")
m.checkCACertTrusted(summary, "192.0.2.10")
if summary.CACertTrusted {
t.Errorf("Expected CACertTrusted to be false when nothing is found")
}
@@ -486,7 +486,7 @@ func TestTestConnection(t *testing.T) {
}
// Test 1: Shared trust store (no explicit CA)
output, err := m.TestConnection("192.168.1.10", "https://localhost:8443/health", false)
output, err := m.TestConnection("192.0.2.10", "https://localhost:8443/health", false)
if err != nil {
t.Fatalf("TestConnection failed: %v", err)
}
@@ -498,7 +498,7 @@ func TestTestConnection(t *testing.T) {
}
// Test 2: Explicit CA
output, err = m.TestConnection("192.168.1.10", "https://localhost:8443/health", true)
output, err = m.TestConnection("192.0.2.10", "https://localhost:8443/health", true)
if err != nil {
t.Fatalf("TestConnection failed: %v", err)
}
@@ -585,7 +585,7 @@ func TestTestHostsRedirection(t *testing.T) {
return mock
}
output, err := m.TestHostsRedirection("192.168.1.10", "http://1.2.3.4:8000")
output, err := m.TestHostsRedirection("192.0.2.10", "http://1.2.3.4:8000")
if err != nil {
t.Fatalf("TestHostsRedirection failed: %v", err)
}
@@ -761,7 +761,7 @@ func TestMigrateViaHosts_SkipCAIfTrusted(t *testing.T) {
t.Fatalf("Failed to ensure CA: %v", err)
}
m := NewManager("http://192.168.1.100:8000", nil, cm)
m := NewManager("http://192.0.2.100:8000", nil, cm)
runCalls := []string{}
m.NewSSH = func(host string) SSHClient {
@@ -771,7 +771,7 @@ func TestMigrateViaHosts_SkipCAIfTrusted(t *testing.T) {
if command == "cat /etc/hosts" {
// Handle both initial read and verification read
if len(runCalls) > 2 { // Rough heuristic: verification happens after upload
return "192.168.1.100\tstreaming.bose.com\n192.168.1.100\tupdates.bose.com\n192.168.1.100\tstats.bose.com\n192.168.1.100\tbmx.bose.com\n192.168.1.100\tcontent.api.bose.io\n192.168.1.100\tevents.api.bosecm.com\n192.168.1.100\tbose-prod.apigee.net\n192.168.1.100\tworldwide.bose.com\n192.168.1.100\tmedia.bose.io\n192.168.1.100\tdownloads.bose.com\n192.168.1.100\tvoice.api.bose.io", nil
return "192.0.2.100\tstreaming.bose.com\n192.0.2.100\tupdates.bose.com\n192.0.2.100\tstats.bose.com\n192.0.2.100\tbmx.bose.com\n192.0.2.100\tcontent.api.bose.io\n192.0.2.100\tevents.api.bosecm.com\n192.0.2.100\tbose-prod.apigee.net\n192.0.2.100\tworldwide.bose.com\n192.0.2.100\tmedia.bose.io\n192.0.2.100\tdownloads.bose.com\n192.0.2.100\tvoice.api.bose.io", nil
}
return "127.0.0.1 localhost", nil
}
@@ -784,7 +784,7 @@ func TestMigrateViaHosts_SkipCAIfTrusted(t *testing.T) {
}
}
_, err = m.migrateViaHosts("192.168.1.10", "http://192.168.1.100:8000")
_, err = m.migrateViaHosts("192.0.2.10", "http://192.0.2.100:8000")
if err != nil {
t.Fatalf("migrateViaHosts failed: %v", err)
}
@@ -838,7 +838,7 @@ func TestTrustCACert(t *testing.T) {
}
}
_, err = m.TrustCACert("192.168.1.10")
_, err = m.TrustCACert("192.0.2.10")
if err != nil {
t.Fatalf("TrustCACert failed: %v", err)
}
@@ -967,7 +967,7 @@ func TestTrustCACert_StripsMultipleStaleEntriesSilently(t *testing.T) {
return sshMock
}
logs, err := m.TrustCACert("192.168.1.10")
logs, err := m.TrustCACert("192.0.2.10")
if err != nil {
t.Fatalf("TrustCACert failed: %v", err)
}
@@ -1084,7 +1084,7 @@ func TestTrustCACert_PostUploadVerificationFailureCleansUpTmp(t *testing.T) {
return sshMock
}
_, err = m.TrustCACert("192.168.1.10")
_, err = m.TrustCACert("192.0.2.10")
if err == nil {
t.Fatalf("TrustCACert succeeded, want a verification failure")
}
@@ -1149,7 +1149,7 @@ func TestRevertMigration(t *testing.T) {
}
}
_, err := m.RevertMigration("192.168.1.10")
_, err := m.RevertMigration("192.0.2.10")
if err != nil {
t.Fatalf("RevertMigration failed: %v", err)
}
@@ -1262,7 +1262,7 @@ func TestRevertMigration_CorruptedRcLocal(t *testing.T) {
}
}
_, err := m.RevertMigration("192.168.1.10")
_, err := m.RevertMigration("192.0.2.10")
if err != nil {
t.Fatalf("RevertMigration failed: %v", err)
}
@@ -1293,7 +1293,7 @@ func TestRevertMigration_NoBackup(t *testing.T) {
}
}
_, err := m.RevertMigration("192.168.1.10")
_, err := m.RevertMigration("192.0.2.10")
if err == nil {
t.Errorf("Expected error when backup is missing, got nil")
} else if !strings.Contains(err.Error(), "backup") {
@@ -1314,7 +1314,7 @@ func TestReboot(t *testing.T) {
}
}
_, err := m.Reboot("192.168.1.10", "")
_, err := m.Reboot("192.0.2.10", "")
if err != nil {
t.Fatalf("Reboot failed: %v", err)
}
@@ -1332,7 +1332,7 @@ func TestReboot(t *testing.T) {
}
func TestTestDNSRedirection(t *testing.T) {
m := NewManager("http://192.168.1.100:8000", nil, nil)
m := NewManager("http://192.0.2.100:8000", nil, nil)
runCalls := []string{}
m.NewSSH = func(host string) SSHClient {
@@ -1344,29 +1344,29 @@ func TestTestDNSRedirection(t *testing.T) {
if !strings.Contains(command, "\\x00\\x21") {
return "", fmt.Errorf("missing TCP length prefix in nc command")
}
// Mock od output: " 192 168 1 100"
return " 192 168 1 100", nil
// Mock od output: " 192 0 2 100"
return " 192 0 2 100", nil
}
if strings.HasPrefix(command, "nslookup aftertouch.test 192.168.1.100") {
return "Server: 192.168.1.100\nAddress 1: 192.168.1.100\n\nName: aftertouch.test\nAddress 1: 192.168.1.100", nil
if strings.HasPrefix(command, "nslookup aftertouch.test 192.0.2.100") {
return "Server: 192.0.2.100\nAddress 1: 192.0.2.100\n\nName: aftertouch.test\nAddress 1: 192.0.2.100", nil
}
return "", nil
},
}
}
output, err := m.TestDNSRedirection("192.168.1.10", "http://192.168.1.100:8000")
output, err := m.TestDNSRedirection("192.0.2.10", "http://192.0.2.100:8000")
if err != nil {
t.Fatalf("TestDNSRedirection failed: %v", err)
}
if !strings.Contains(output, "192.168.1.100") {
if !strings.Contains(output, "192.0.2.100") {
t.Errorf("Expected output to contain service IP, got %s", output)
}
foundNc := false
for _, call := range runCalls {
if strings.Contains(call, "nc") && !strings.Contains(call, "-u") && strings.Contains(call, "192.168.1.100 53") {
if strings.Contains(call, "nc") && !strings.Contains(call, "-u") && strings.Contains(call, "192.0.2.100 53") {
foundNc = true
break
}
@@ -1389,7 +1389,7 @@ func TestTestDNSRedirection_CustomPort(t *testing.T) {
DNSBindAddr: ":1053",
})
m := NewManager("http://192.168.1.100:8000", ds, nil)
m := NewManager("http://192.0.2.100:8000", ds, nil)
runCalls := []string{}
m.NewSSH = func(host string) SSHClient {
@@ -1401,25 +1401,25 @@ func TestTestDNSRedirection_CustomPort(t *testing.T) {
if !strings.Contains(command, "\\x00\\x21") {
return "", fmt.Errorf("missing TCP length prefix in nc command")
}
return " 192 168 1 100", nil
return " 192 0 2 100", nil
}
return "", nil
},
}
}
output, err := m.TestDNSRedirection("192.168.1.10", "http://192.168.1.100:8000")
output, err := m.TestDNSRedirection("192.0.2.10", "http://192.0.2.100:8000")
if err != nil {
t.Fatalf("TestDNSRedirection failed: %v", err)
}
if !strings.Contains(output, "192.168.1.100") {
if !strings.Contains(output, "192.0.2.100") {
t.Errorf("Expected output to contain service IP, got %s", output)
}
foundNc := false
for _, call := range runCalls {
if strings.Contains(call, "nc") && !strings.Contains(call, "-u") && strings.Contains(call, "192.168.1.100 1053") {
if strings.Contains(call, "nc") && !strings.Contains(call, "-u") && strings.Contains(call, "192.0.2.100 1053") {
foundNc = true
break
}
@@ -1442,7 +1442,7 @@ func TestBackupConfigOffDevice(t *testing.T) {
m := NewManager("http://localhost:8000", ds, nil)
serial := "08DF1F0BA325"
accountID := "3230304"
accountID := "1000001"
// Mock info server
infoServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -1462,7 +1462,7 @@ func TestBackupConfigOffDevice(t *testing.T) {
return "<SoundTouchSdkPrivateCfg><margeServerUrl>http://original</margeServerUrl></SoundTouchSdkPrivateCfg>", nil
}
if strings.Contains(command, "/etc/hosts") {
return "127.0.0.1 localhost\n192.168.1.1 bmx.bose.com", nil
return "127.0.0.1 localhost\n192.0.2.1 bmx.bose.com", nil
}
return "", nil
},
@@ -1512,7 +1512,7 @@ func TestMigrateSpeaker_PreFlightFailure(t *testing.T) {
}
}
_, err := m.MigrateSpeaker("192.168.1.10", "", "", nil, MigrationMethodXML)
_, err := m.MigrateSpeaker("192.0.2.10", "", "", nil, MigrationMethodXML)
if err == nil {
t.Errorf("Expected error during pre-flight write check, got nil")
}
@@ -1533,7 +1533,7 @@ func TestMigrateViaResolvConf(t *testing.T) {
t.Fatalf("Failed to ensure CA: %v", err)
}
m := NewManager("http://192.168.1.100:8000", nil, cm)
m := NewManager("http://192.0.2.100:8000", nil, cm)
runCalls := []string{}
uploads := make(map[string]string)
@@ -1560,13 +1560,13 @@ func TestMigrateViaResolvConf(t *testing.T) {
}
}
_, err = m.migrateViaResolvConf("192.168.1.10", "http://192.168.1.100:8000")
_, err = m.migrateViaResolvConf("192.0.2.10", "http://192.0.2.100:8000")
if err != nil {
t.Fatalf("migrateViaResolvConf failed: %v", err)
}
// Verify uploads
if !strings.Contains(uploads["/mnt/nv/soundtouch-service/aftertouch.resolv.conf"], "nameserver 192.168.1.100") {
if !strings.Contains(uploads["/mnt/nv/soundtouch-service/aftertouch.resolv.conf"], "nameserver 192.0.2.100") {
t.Errorf("aftertouch.resolv.conf missing nameserver")
}
@@ -1599,7 +1599,7 @@ func TestMigrateViaResolvConf_CorruptedRcLocal(t *testing.T) {
t.Fatalf("Failed to ensure CA: %v", err)
}
m := NewManager("http://192.168.1.100:8000", nil, cm)
m := NewManager("http://192.0.2.100:8000", nil, cm)
uploads := make(map[string]string)
@@ -1625,7 +1625,7 @@ func TestMigrateViaResolvConf_CorruptedRcLocal(t *testing.T) {
}
}
_, err = m.migrateViaResolvConf("192.168.1.10", "http://192.168.1.100:8000")
_, err = m.migrateViaResolvConf("192.0.2.10", "http://192.0.2.100:8000")
if err != nil {
t.Fatalf("migrateViaResolvConf failed: %v", err)
}
@@ -1655,7 +1655,7 @@ func TestMigrateViaResolvConf_UdhcpcScript(t *testing.T) {
t.Fatalf("Failed to ensure CA: %v", err)
}
m := NewManager("http://192.168.1.100:8000", nil, cm)
m := NewManager("http://192.0.2.100:8000", nil, cm)
runCalls := []string{}
uploads := make(map[string]string)
@@ -1687,7 +1687,7 @@ func TestMigrateViaResolvConf_UdhcpcScript(t *testing.T) {
}
}
_, err = m.migrateViaResolvConf("192.168.1.10", "http://192.168.1.100:8000")
_, err = m.migrateViaResolvConf("192.0.2.10", "http://192.0.2.100:8000")
if err != nil {
t.Fatalf("migrateViaResolvConf failed: %v", err)
}
@@ -1725,7 +1725,7 @@ func TestRevertMigration_ResolvConf(t *testing.T) {
}
defer os.RemoveAll(tempDir)
m := NewManager("http://192.168.1.100:8000", nil, nil)
m := NewManager("http://192.0.2.100:8000", nil, nil)
runCalls := []string{}
uploads := make(map[string]string)
@@ -1754,7 +1754,7 @@ func TestRevertMigration_ResolvConf(t *testing.T) {
}
}
_, err = m.RevertMigration("192.168.1.10")
_, err = m.RevertMigration("192.0.2.10")
if err != nil {
t.Fatalf("RevertMigration failed: %v", err)
}
@@ -1843,7 +1843,7 @@ func TestCheckIsMigrated(t *testing.T) {
summary := &MigrationSummary{
SSHSuccess: true,
CACertTrusted: true,
CurrentResolvConf: "# Priority nameserver for Bose service redirection\nnameserver 192.168.1.1\n",
CurrentResolvConf: "# Priority nameserver for Bose service redirection\nnameserver 192.0.2.1\n",
}
m.checkIsMigrated(summary, "127.0.0.1")
if !summary.IsMigrated {
@@ -1946,7 +1946,7 @@ func TestMigrateSpeaker_ResolvBlocking(t *testing.T) {
ds := datastore.NewDataStore(tempDir)
cm := certmanager.NewCertificateManager(filepath.Join(tempDir, "certs"))
m := NewManager("http://192.168.1.100:8000", ds, cm)
m := NewManager("http://192.0.2.100:8000", ds, cm)
m.NewSSH = func(host string) SSHClient {
return &mockSSH{
+9 -9
View File
@@ -24,7 +24,7 @@ func TestSyncDeviceData_UsesDeviceID(t *testing.T) {
// Return device info with MAC address as deviceID
w.Header().Set("Content-Type", "application/xml")
fmt.Fprint(w, `<?xml version="1.0" encoding="UTF-8"?>
<info deviceID="A81B6A536A98">
<info deviceID="AABBCCDDEEFF">
<name>Test Device</name>
<type>SoundTouch 30</type>
<margeAccountUUID>test-account-123</margeAccountUUID>
@@ -36,20 +36,20 @@ func TestSyncDeviceData_UsesDeviceID(t *testing.T) {
</component>
</components>
<networkInfo type="SCM">
<macAddress>A81B6A536A98</macAddress>
<ipAddress>192.168.1.100</ipAddress>
<macAddress>AABBCCDDEEFF</macAddress>
<ipAddress>192.0.2.100</ipAddress>
</networkInfo>
</info>`)
case "/presets":
// Return empty presets for simplicity
w.Header().Set("Content-Type", "application/xml")
fmt.Fprint(w, `<?xml version="1.0" encoding="UTF-8"?>
<presets deviceID="A81B6A536A98"/>`)
<presets deviceID="AABBCCDDEEFF"/>`)
case "/recents":
// Return empty recents for simplicity
w.Header().Set("Content-Type", "application/xml")
fmt.Fprint(w, `<?xml version="1.0" encoding="UTF-8"?>
<recents deviceID="A81B6A536A98"/>`)
<recents deviceID="AABBCCDDEEFF"/>`)
default:
http.NotFound(w, r)
}
@@ -70,7 +70,7 @@ func TestSyncDeviceData_UsesDeviceID(t *testing.T) {
}
// Verify that data was synced to the correct directory using MAC address (deviceID)
expectedDeviceID := "A81B6A536A98"
expectedDeviceID := "AABBCCDDEEFF"
expectedAccountID := "test-account-123"
// Check that the device directory is resolved correctly using MAC address
@@ -151,7 +151,7 @@ func TestSyncDeviceData_FallbackToExistingDeviceMapping(t *testing.T) {
// Pre-populate device data using serial number (legacy scenario)
accountID := "test-account-123"
serialNumber := "I6332527703739342000020"
macAddress := "A81B6A536A98"
macAddress := "AABBCCDDEEFF"
// Save device info under serial number (simulating legacy behavior)
legacyDeviceInfo := &models.ServiceDeviceInfo{
@@ -207,11 +207,11 @@ func TestSyncDeviceData_FallbackToExistingDeviceMapping(t *testing.T) {
case "/presets":
w.Header().Set("Content-Type", "application/xml")
fmt.Fprint(w, `<?xml version="1.0" encoding="UTF-8"?>
<presets deviceID="A81B6A536A98"/>`)
<presets deviceID="AABBCCDDEEFF"/>`)
case "/recents":
w.Header().Set("Content-Type", "application/xml")
fmt.Fprint(w, `<?xml version="1.0" encoding="UTF-8"?>
<recents deviceID="A81B6A536A98"/>`)
<recents deviceID="AABBCCDDEEFF"/>`)
}
}))
defer server.Close()
+10 -10
View File
@@ -211,8 +211,8 @@ func TestWaitForOnline_MatchesSubstringInNameOrSerial(t *testing.T) {
results: [][]*models.DiscoveredDevice{
nil, // first poll: nothing yet
{
{Name: "Other Bose Speaker", SerialNo: "AAAAAAAAAAAA", Host: "192.168.1.50"},
{Name: "Bose SoundTouch DE4803", SerialNo: "506583DE4803", Host: "192.168.1.42"},
{Name: "Other Bose Speaker", SerialNo: "AAAAAAAAAAAA", Host: "192.0.2.50"},
{Name: "Bose SoundTouch DE4803", SerialNo: "506583DE4803", Host: "192.0.2.42"},
},
},
}
@@ -224,8 +224,8 @@ func TestWaitForOnline_MatchesSubstringInNameOrSerial(t *testing.T) {
t.Fatalf("WaitForOnline: %v", err)
}
if d.Host != "192.168.1.42" {
t.Errorf("Host = %q, want 192.168.1.42", d.Host)
if d.Host != "192.0.2.42" {
t.Errorf("Host = %q, want 192.0.2.42", d.Host)
}
}
@@ -233,7 +233,7 @@ func TestWaitForOnline_EmptyMatcherReturnsFirst(t *testing.T) {
stub := &stubMDNS{
results: [][]*models.DiscoveredDevice{
{
{Name: "Bose SoundTouch DE4803", Host: "192.168.1.42"},
{Name: "Bose SoundTouch DE4803", Host: "192.0.2.42"},
},
},
}
@@ -245,17 +245,17 @@ func TestWaitForOnline_EmptyMatcherReturnsFirst(t *testing.T) {
t.Fatalf("WaitForOnline: %v", err)
}
if d.Host != "192.168.1.42" {
t.Errorf("Host = %q, want 192.168.1.42", d.Host)
if d.Host != "192.0.2.42" {
t.Errorf("Host = %q, want 192.0.2.42", d.Host)
}
}
func TestWaitForOnline_TimesOutWhenNoMatch(t *testing.T) {
stub := &stubMDNS{
results: [][]*models.DiscoveredDevice{
{{Name: "Wrong One", SerialNo: "X", Host: "192.168.1.99"}},
{{Name: "Wrong One", SerialNo: "X", Host: "192.168.1.99"}},
{{Name: "Wrong One", SerialNo: "X", Host: "192.168.1.99"}},
{{Name: "Wrong One", SerialNo: "X", Host: "192.0.2.99"}},
{{Name: "Wrong One", SerialNo: "X", Host: "192.0.2.99"}},
{{Name: "Wrong One", SerialNo: "X", Host: "192.0.2.99"}},
},
}
+2 -2
View File
@@ -125,8 +125,8 @@ func TestBridge_DeviceDiscovery_CumulativeList(t *testing.T) {
// Simulate what runDeviceDiscovery now does: build a cumulative slice and
// enqueue it with every new device.
d1 := RendererDevice{UID: "AABBCC112233", IP: "192.168.1.10"}
d2 := RendererDevice{UID: "DDEEFF445566", IP: "192.168.1.11"}
d1 := RendererDevice{UID: "AABBCC112233", IP: "192.0.2.10"}
d2 := RendererDevice{UID: "DDEEFF445566", IP: "192.0.2.11"}
var seen []RendererDevice
for _, d := range []RendererDevice{d1, d2} {
+8 -3
View File
@@ -324,19 +324,24 @@ func TestValidateZcBaseURL(t *testing.T) {
}{
{"loopback", "http://127.0.0.1:8200/zc", true, "127.0.0.1:8200", "/zc"},
{"loopback no port", "http://127.0.0.1/zc", true, "127.0.0.1", "/zc"},
{"private 192", "http://192.168.1.10:8200/zc", true, "192.168.1.10:8200", "/zc"},
// The "private 192" and "strips query" cases must use an
// RFC-1918 192.168/16 value — validateZcBaseURL only accepts
// loopback, RFC-1918, and link-local. RFC-5737 doc IPs
// (which we use as placeholders elsewhere) would be rejected
// here, so the test uses a generic-but-real 192.168 value.
{"private 192", "http://192.168.10.10:8200/zc", true, "192.168.10.10:8200", "/zc"},
{"private 10", "http://10.0.0.5/zc", true, "10.0.0.5", "/zc"},
{"private 172", "http://172.16.0.1/zc", true, "172.16.0.1", "/zc"},
{"link-local v4", "http://169.254.10.20/zc", true, "169.254.10.20", "/zc"},
{"ipv6 loopback", "http://[::1]:8200/zc", true, "[::1]:8200", "/zc"},
{"ipv6 link-local", "http://[fe80::1]:8200/zc", true, "[fe80::1]:8200", "/zc"},
{"strips query", "http://192.168.1.10:8200/zc?foo=bar", true, "192.168.1.10:8200", "/zc"},
{"strips query", "http://192.168.10.10:8200/zc?foo=bar", true, "192.168.10.10:8200", "/zc"},
{"public IP rejected", "http://1.1.1.1/zc", false, "", ""},
{"public ipv6 rejected", "http://[2001:db8::1]/zc", false, "", ""},
{"hostname rejected", "http://myspeaker.local/zc", false, "", ""},
{"plain hostname rejected", "http://speaker/zc", false, "", ""},
{"ftp scheme rejected", "ftp://192.168.1.10/zc", false, "", ""},
{"ftp scheme rejected", "ftp://192.0.2.10/zc", false, "", ""},
{"file scheme rejected", "file:///etc/passwd", false, "", ""},
{"empty host rejected", "http:///zc", false, "", ""},
{"unparseable rejected", "::not a url::", false, "", ""},
+1 -1
View File
@@ -6,7 +6,7 @@ import (
)
func TestNewClient(t *testing.T) {
host := "192.168.1.10"
host := "192.0.2.10"
client := NewClient(host)
if client.Host != host {
+3 -3
View File
@@ -158,9 +158,9 @@ func newClientFor(t *testing.T, s *scriptedServer) *Client {
}
func TestNewClient_Defaults(t *testing.T) {
c := NewClient("192.168.1.10")
if c.Host != "192.168.1.10" {
t.Errorf("Host = %q, want 192.168.1.10", c.Host)
c := NewClient("192.0.2.10")
if c.Host != "192.0.2.10" {
t.Errorf("Host = %q, want 192.0.2.10", c.Host)
}
if c.Port != DefaultPort {
@@ -22,7 +22,7 @@ Authorization: Bearer dummy-token
const expectedDeviceId = "B05ECAFE";
const expectedName = "SoundTouch-11";
const expectedIp = "192.168.1.100";
const expectedIp = "192.0.2.100";
const device = deviceList.item(0);
client.assert(device.getAttribute("deviceid") === expectedDeviceId, "Wrong device ID");
@@ -7,7 +7,7 @@
### in a loop and the Bose App showed the rename spinning forever.
### This test runs after register_device.http (which creates the
### initial record) and after power_on.http (which seeds the IP at
### 192.168.1.100). The rename PUT must:
### 192.0.2.100). The rename PUT must:
###
### - return 200 OK
### - echo the new name
@@ -54,7 +54,7 @@ Authorization: Bearer {{token}}
// register_device.http captured on the initial POST. If it
// doesn't, the rename PUT regressed the "first-paired in
// 2017" semantics — the exact bug behind the parity capture
// at data/parity_mismatches/1771797308__streaming_account_3230304_device_A81B6A536A98.json.
// at data/parity_mismatches/1771797308__streaming_account_1000001_device_AABBCCDDEEFF.json.
const initial = client.global.get("initialCreatedOn");
client.assert(initial !== undefined && initial.length > 0,
"initialCreatedOn was not captured by register_device.http — test ordering issue");