mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
Removing one member from a multi-member zone did nothing. The remove paths rebuilt the zone with /setZone and the remaining members, but /setZone is additive: it never drops a member that is simply absent from the list. It only "removed" when the resulting set was empty (equivalent to dissolve), which is why removing the last member worked but removing one of several did not. Switch all three remove paths to the dedicated /removeZoneSlave endpoint (already implemented as client.RemoveZoneSlave): - HandleZoneRemove (web UI "remove member") - HandleZoneLeave (web UI slave "leave zone") - RemoveFromZone (client lib, used by CLI `zone remove`) DissolveZone (setZone master-only) and HandleZoneAdd (additive setZone) are correct and unchanged. Adds handler regression tests for remove/leave and rewrites TestClient_RemoveFromZone to assert /removeZoneSlave (the old test removed one of two members but only checked that setZone was called, never that the member was dropped). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
719 lines
18 KiB
Go
719 lines
18 KiB
Go
package client
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gesellix/bose-soundtouch/pkg/models"
|
|
)
|
|
|
|
func TestClient_GetZone(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
responseXML string
|
|
responseStatus int
|
|
expectError bool
|
|
expectedMaster string
|
|
expectedMembers int
|
|
}{
|
|
{
|
|
name: "Standalone device",
|
|
responseXML: `<?xml version="1.0" encoding="UTF-8" ?>
|
|
<zone master="ABCD1234EFGH"></zone>`,
|
|
responseStatus: http.StatusOK,
|
|
expectError: false,
|
|
expectedMaster: "ABCD1234EFGH",
|
|
expectedMembers: 0,
|
|
},
|
|
{
|
|
name: "Zone with members",
|
|
responseXML: `<?xml version="1.0" encoding="UTF-8" ?>
|
|
<zone master="ABCD1234EFGH">
|
|
<member ipaddress="192.0.2.11">EFGH5678IJKL</member>
|
|
<member ipaddress="192.0.2.12">IJKL9012MNOP</member>
|
|
</zone>`,
|
|
responseStatus: http.StatusOK,
|
|
expectError: false,
|
|
expectedMaster: "ABCD1234EFGH",
|
|
expectedMembers: 2,
|
|
},
|
|
{
|
|
name: "Server error",
|
|
responseXML: `<error>Server Error</error>`,
|
|
responseStatus: http.StatusInternalServerError,
|
|
expectError: true,
|
|
expectedMaster: "",
|
|
expectedMembers: 0,
|
|
},
|
|
{
|
|
name: "Device not found",
|
|
responseXML: `<?xml version="1.0" encoding="UTF-8" ?>
|
|
<errors deviceID="ABCD1234EFGH">
|
|
<error value="7" name="DEVICE_NOT_FOUND_ERROR">Device not found</error>
|
|
</errors>`,
|
|
responseStatus: http.StatusNotFound,
|
|
expectError: true,
|
|
expectedMaster: "",
|
|
expectedMembers: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/getZone" {
|
|
t.Errorf("Expected path /getZone, got %s", r.URL.Path)
|
|
}
|
|
|
|
if r.Method != http.MethodGet {
|
|
t.Errorf("Expected GET method, got %s", r.Method)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
w.WriteHeader(tt.responseStatus)
|
|
_, _ = w.Write([]byte(tt.responseXML))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := createTestClient(server.URL)
|
|
zone, err := client.GetZone()
|
|
|
|
if tt.expectError {
|
|
if err == nil {
|
|
t.Error("Expected error, but got none")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Errorf("Expected no error, but got: %v", err)
|
|
return
|
|
}
|
|
|
|
if zone.Master != tt.expectedMaster {
|
|
t.Errorf("Expected master %s, got %s", tt.expectedMaster, zone.Master)
|
|
}
|
|
|
|
if len(zone.Members) != tt.expectedMembers {
|
|
t.Errorf("Expected %d members, got %d", tt.expectedMembers, len(zone.Members))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_SetZone(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
zoneRequest *models.ZoneRequest
|
|
responseStatus int
|
|
expectError bool
|
|
errorMessage string
|
|
}{
|
|
{
|
|
name: "Valid zone request",
|
|
zoneRequest: func() *models.ZoneRequest {
|
|
zr := models.NewZoneRequest("ABCD1234EFGH")
|
|
zr.AddMember("EFGH5678IJKL", "192.0.2.11")
|
|
|
|
return zr
|
|
}(),
|
|
responseStatus: http.StatusOK,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Standalone zone request",
|
|
zoneRequest: models.NewZoneRequest("ABCD1234EFGH"),
|
|
responseStatus: http.StatusOK,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Invalid zone request - empty master",
|
|
zoneRequest: &models.ZoneRequest{},
|
|
expectError: true,
|
|
errorMessage: "invalid zone request: master device ID is required",
|
|
},
|
|
{
|
|
name: "Invalid zone request - duplicate device",
|
|
zoneRequest: func() *models.ZoneRequest {
|
|
zr := models.NewZoneRequest("ABCD1234EFGH")
|
|
zr.AddMember("ABCD1234EFGH", "192.0.2.10") // Same as master
|
|
|
|
return zr
|
|
}(),
|
|
expectError: true,
|
|
errorMessage: "invalid zone request: duplicate device ID found: ABCD1234EFGH",
|
|
},
|
|
{
|
|
name: "Server error response",
|
|
zoneRequest: models.NewZoneRequest("ABCD1234EFGH"),
|
|
responseStatus: http.StatusInternalServerError,
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/setZone" {
|
|
t.Errorf("Expected path /setZone, got %s", r.URL.Path)
|
|
}
|
|
|
|
if r.Method != http.MethodPost {
|
|
t.Errorf("Expected POST method, got %s", r.Method)
|
|
}
|
|
|
|
// Check Content-Type
|
|
contentType := r.Header.Get("Content-Type")
|
|
if contentType != "application/xml" {
|
|
t.Errorf("Expected Content-Type application/xml, got %s", contentType)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
w.WriteHeader(tt.responseStatus)
|
|
|
|
if tt.responseStatus != http.StatusOK {
|
|
_, _ = w.Write([]byte(`<error>Server Error</error>`))
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := createTestClient(server.URL)
|
|
err := client.SetZone(tt.zoneRequest)
|
|
|
|
if tt.expectError {
|
|
if err == nil {
|
|
t.Error("Expected error, but got none")
|
|
} else if tt.errorMessage != "" && err.Error() != tt.errorMessage {
|
|
t.Errorf("Expected error message '%s', got '%s'", tt.errorMessage, err.Error())
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Errorf("Expected no error, but got: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_CreateZone(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/setZone" {
|
|
t.Errorf("Expected path /setZone, got %s", r.URL.Path)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := createTestClient(server.URL)
|
|
|
|
masterDeviceID := "ABCD1234EFGH"
|
|
memberDeviceIDs := []string{"EFGH5678IJKL", "IJKL9012MNOP"}
|
|
|
|
err := client.CreateZone(masterDeviceID, memberDeviceIDs)
|
|
if err != nil {
|
|
t.Errorf("Expected no error, but got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClient_CreateZoneWithIPs(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/setZone" {
|
|
t.Errorf("Expected path /setZone, got %s", r.URL.Path)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := createTestClient(server.URL)
|
|
|
|
masterDeviceID := "ABCD1234EFGH"
|
|
members := map[string]string{
|
|
"EFGH5678IJKL": "192.0.2.11",
|
|
"IJKL9012MNOP": "192.0.2.12",
|
|
}
|
|
|
|
err := client.CreateZoneWithIPs(masterDeviceID, members)
|
|
if err != nil {
|
|
t.Errorf("Expected no error, but got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClient_AddToZone(t *testing.T) {
|
|
getZoneCalled := false
|
|
setZoneCalled := false
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
|
|
if r.URL.Path == "/getZone" && r.Method == http.MethodGet {
|
|
getZoneCalled = true
|
|
// Return existing zone
|
|
response := `<?xml version="1.0" encoding="UTF-8" ?>
|
|
<zone master="ABCD1234EFGH">
|
|
<member ipaddress="192.0.2.11">EFGH5678IJKL</member>
|
|
</zone>`
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(response))
|
|
} else if r.URL.Path == "/setZone" && r.Method == http.MethodPost {
|
|
setZoneCalled = true
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
} else {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := createTestClient(server.URL)
|
|
|
|
err := client.AddToZone("IJKL9012MNOP", "192.0.2.12")
|
|
if err != nil {
|
|
t.Errorf("Expected no error, but got: %v", err)
|
|
}
|
|
|
|
if !getZoneCalled {
|
|
t.Error("Expected GetZone to be called")
|
|
}
|
|
|
|
if !setZoneCalled {
|
|
t.Error("Expected SetZone to be called")
|
|
}
|
|
}
|
|
|
|
func TestClient_RemoveFromZone(t *testing.T) {
|
|
getZoneCalled := false
|
|
setZoneCalled := false
|
|
|
|
var removeBody string
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
|
|
switch {
|
|
case r.URL.Path == "/getZone" && r.Method == http.MethodGet:
|
|
getZoneCalled = true
|
|
// Return existing zone with two members.
|
|
response := `<?xml version="1.0" encoding="UTF-8" ?>
|
|
<zone master="ABCD1234EFGH">
|
|
<member ipaddress="192.0.2.11">EFGH5678IJKL</member>
|
|
<member ipaddress="192.0.2.12">IJKL9012MNOP</member>
|
|
</zone>`
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(response))
|
|
case r.URL.Path == "/removeZoneSlave" && r.Method == http.MethodPost:
|
|
b, _ := io.ReadAll(r.Body)
|
|
removeBody = string(b)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
case r.URL.Path == "/setZone" && r.Method == http.MethodPost:
|
|
setZoneCalled = true
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
default:
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := createTestClient(server.URL)
|
|
|
|
// Removing one of two members must target that member via /removeZoneSlave,
|
|
// not rebuild the zone via /setZone (which does not drop a member from a
|
|
// multi-member zone). Regression for #511.
|
|
err := client.RemoveFromZone("EFGH5678IJKL")
|
|
if err != nil {
|
|
t.Errorf("Expected no error, but got: %v", err)
|
|
}
|
|
|
|
if !getZoneCalled {
|
|
t.Error("Expected GetZone to be called")
|
|
}
|
|
|
|
if setZoneCalled {
|
|
t.Error("RemoveFromZone must not use /setZone to drop a member from a multi-member zone")
|
|
}
|
|
|
|
if !strings.Contains(removeBody, "EFGH5678IJKL") {
|
|
t.Errorf("removeZoneSlave body should target the member, got: %s", removeBody)
|
|
}
|
|
|
|
if !strings.Contains(removeBody, `master="ABCD1234EFGH"`) {
|
|
t.Errorf("removeZoneSlave body should name the master, got: %s", removeBody)
|
|
}
|
|
|
|
if !strings.Contains(removeBody, `ipaddress="192.0.2.11"`) {
|
|
t.Errorf("removeZoneSlave body should carry the member IP from the zone, got: %s", removeBody)
|
|
}
|
|
}
|
|
|
|
func TestClient_DissolveZone(t *testing.T) {
|
|
getZoneCalled := false
|
|
setZoneCalled := false
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
|
|
if r.URL.Path == "/getZone" && r.Method == http.MethodGet {
|
|
getZoneCalled = true
|
|
// Return existing zone with members
|
|
response := `<?xml version="1.0" encoding="UTF-8" ?>
|
|
<zone master="ABCD1234EFGH">
|
|
<member ipaddress="192.0.2.11">EFGH5678IJKL</member>
|
|
<member ipaddress="192.0.2.12">IJKL9012MNOP</member>
|
|
</zone>`
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(response))
|
|
} else if r.URL.Path == "/setZone" && r.Method == http.MethodPost {
|
|
setZoneCalled = true
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
} else {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := createTestClient(server.URL)
|
|
|
|
err := client.DissolveZone()
|
|
if err != nil {
|
|
t.Errorf("Expected no error, but got: %v", err)
|
|
}
|
|
|
|
if !getZoneCalled {
|
|
t.Error("Expected GetZone to be called")
|
|
}
|
|
|
|
if !setZoneCalled {
|
|
t.Error("Expected SetZone to be called")
|
|
}
|
|
}
|
|
|
|
func TestClient_IsInZone(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
responseXML string
|
|
expectedResult bool
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "Standalone device",
|
|
responseXML: `<?xml version="1.0" encoding="UTF-8" ?>
|
|
<zone master="ABCD1234EFGH"></zone>`,
|
|
expectedResult: false,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Device in zone",
|
|
responseXML: `<?xml version="1.0" encoding="UTF-8" ?>
|
|
<zone master="ABCD1234EFGH">
|
|
<member ipaddress="192.0.2.11">EFGH5678IJKL</member>
|
|
</zone>`,
|
|
expectedResult: true,
|
|
expectError: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/getZone" {
|
|
t.Errorf("Expected path /getZone, got %s", r.URL.Path)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(tt.responseXML))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := createTestClient(server.URL)
|
|
result, err := client.IsInZone()
|
|
|
|
if tt.expectError {
|
|
if err == nil {
|
|
t.Error("Expected error, but got none")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Errorf("Expected no error, but got: %v", err)
|
|
return
|
|
}
|
|
|
|
if result != tt.expectedResult {
|
|
t.Errorf("Expected result %t, got %t", tt.expectedResult, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_GetZoneStatus(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
deviceInfoXML string
|
|
zoneXML string
|
|
expectedStatus models.ZoneStatus
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "Standalone device",
|
|
deviceInfoXML: `<?xml version="1.0" encoding="UTF-8" ?>
|
|
<info deviceID="ABCD1234EFGH">
|
|
<name>Living Room</name>
|
|
<type>SoundTouch 20</type>
|
|
</info>`,
|
|
zoneXML: `<?xml version="1.0" encoding="UTF-8" ?>
|
|
<zone master="ABCD1234EFGH"></zone>`,
|
|
expectedStatus: models.ZoneStatusStandalone,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Zone master",
|
|
deviceInfoXML: `<?xml version="1.0" encoding="UTF-8" ?>
|
|
<info deviceID="ABCD1234EFGH">
|
|
<name>Living Room</name>
|
|
<type>SoundTouch 20</type>
|
|
</info>`,
|
|
zoneXML: `<?xml version="1.0" encoding="UTF-8" ?>
|
|
<zone master="ABCD1234EFGH">
|
|
<member ipaddress="192.0.2.11">EFGH5678IJKL</member>
|
|
</zone>`,
|
|
expectedStatus: models.ZoneStatusMaster,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Zone member",
|
|
deviceInfoXML: `<?xml version="1.0" encoding="UTF-8" ?>
|
|
<info deviceID="EFGH5678IJKL">
|
|
<name>Kitchen</name>
|
|
<type>SoundTouch 10</type>
|
|
</info>`,
|
|
zoneXML: `<?xml version="1.0" encoding="UTF-8" ?>
|
|
<zone master="ABCD1234EFGH">
|
|
<member ipaddress="192.0.2.11">EFGH5678IJKL</member>
|
|
</zone>`,
|
|
expectedStatus: models.ZoneStatusSlave,
|
|
expectError: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
switch r.URL.Path {
|
|
case "/getZone":
|
|
_, _ = w.Write([]byte(tt.zoneXML))
|
|
case "/info":
|
|
_, _ = w.Write([]byte(tt.deviceInfoXML))
|
|
default:
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := createTestClient(server.URL)
|
|
status, err := client.GetZoneStatus()
|
|
|
|
if tt.expectError {
|
|
if err == nil {
|
|
t.Error("Expected error, but got none")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Errorf("Expected no error, but got: %v", err)
|
|
return
|
|
}
|
|
|
|
if status != tt.expectedStatus {
|
|
t.Errorf("Expected status %v, got %v", tt.expectedStatus, status)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_GetZoneMembers(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
responseXML string
|
|
expectedMembers []string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "Standalone device",
|
|
responseXML: `<?xml version="1.0" encoding="UTF-8" ?>
|
|
<zone master="ABCD1234EFGH"></zone>`,
|
|
expectedMembers: []string{"ABCD1234EFGH"},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "Zone with members",
|
|
responseXML: `<?xml version="1.0" encoding="UTF-8" ?>
|
|
<zone master="ABCD1234EFGH">
|
|
<member ipaddress="192.0.2.11">EFGH5678IJKL</member>
|
|
<member ipaddress="192.0.2.12">IJKL9012MNOP</member>
|
|
</zone>`,
|
|
expectedMembers: []string{"ABCD1234EFGH", "EFGH5678IJKL", "IJKL9012MNOP"},
|
|
expectError: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/getZone" {
|
|
t.Errorf("Expected path /getZone, got %s", r.URL.Path)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(tt.responseXML))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := createTestClient(server.URL)
|
|
members, err := client.GetZoneMembers()
|
|
|
|
if tt.expectError {
|
|
if err == nil {
|
|
t.Error("Expected error, but got none")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Errorf("Expected no error, but got: %v", err)
|
|
return
|
|
}
|
|
|
|
if len(members) != len(tt.expectedMembers) {
|
|
t.Errorf("Expected %d members, got %d", len(tt.expectedMembers), len(members))
|
|
return
|
|
}
|
|
|
|
for i, expectedMember := range tt.expectedMembers {
|
|
if members[i] != expectedMember {
|
|
t.Errorf("Expected member %s at index %d, got %s", expectedMember, i, members[i])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_Zone_ErrorHandling(t *testing.T) {
|
|
t.Run("GetZone network error", func(t *testing.T) {
|
|
client := NewClientFromHost("invalid.host:9999")
|
|
client.timeout = 100 * time.Millisecond
|
|
|
|
_, err := client.GetZone()
|
|
if err == nil {
|
|
t.Error("Expected network error, but got none")
|
|
}
|
|
})
|
|
|
|
t.Run("SetZone network error", func(t *testing.T) {
|
|
client := NewClientFromHost("invalid.host:9999")
|
|
client.timeout = 100 * time.Millisecond
|
|
|
|
zr := models.NewZoneRequest("DEVICE123")
|
|
|
|
err := client.SetZone(zr)
|
|
if err == nil {
|
|
t.Error("Expected network error, but got none")
|
|
}
|
|
})
|
|
|
|
t.Run("AddToZone - GetZone fails", func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/getZone" {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
_, _ = w.Write([]byte(`<error>Server Error</error>`))
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := createTestClient(server.URL)
|
|
|
|
err := client.AddToZone("DEVICE456", "192.0.2.10")
|
|
if err == nil {
|
|
t.Error("Expected error when GetZone fails, but got none")
|
|
}
|
|
|
|
expectedErrorPrefix := "failed to get current zone:"
|
|
if !strings.Contains(err.Error(), expectedErrorPrefix) {
|
|
t.Errorf("Expected error to contain '%s', got: %v", expectedErrorPrefix, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Benchmark tests
|
|
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.0.2.11">EFGH5678IJKL</member>
|
|
<member ipaddress="192.0.2.12">IJKL9012MNOP</member>
|
|
</zone>`
|
|
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(response))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := createTestClient(server.URL)
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := client.GetZone()
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkClient_SetZone(b *testing.B) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := createTestClient(server.URL)
|
|
zoneRequest := models.NewZoneRequest("ABCD1234EFGH")
|
|
zoneRequest.AddMember("EFGH5678IJKL", "192.0.2.11")
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
err := client.SetZone(zoneRequest)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|