mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-20 09:36:16 +00:00
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:
co-authored by
Claude Opus 4.7
parent
249c2586e9
commit
feadc478d5
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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: "",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
@@ -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"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>`
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,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
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 @@
|
||||
<?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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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}"},
|
||||
|
||||
@@ -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"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"}},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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} {
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user