diff --git a/pkg/service/soundtouchweb/device_projection.go b/pkg/service/soundtouchweb/device_projection.go index 0acf3e87..60504d31 100644 --- a/pkg/service/soundtouchweb/device_projection.go +++ b/pkg/service/soundtouchweb/device_projection.go @@ -228,8 +228,8 @@ func newStereoPairView(group *models.Group, byDeviceID map[string][]deviceProjec for _, role := range group.Roles.Roles { member := stereoPairMemberView{ - DeviceID: role.DeviceID, - Role: role.Role, + DeviceID: strings.TrimSpace(role.DeviceID), + Role: strings.ToUpper(strings.TrimSpace(role.Role)), IPAddress: role.IPAddress, } diff --git a/pkg/service/soundtouchweb/device_projection_test.go b/pkg/service/soundtouchweb/device_projection_test.go index 2a9c0b30..4ab9cedd 100644 --- a/pkg/service/soundtouchweb/device_projection_test.go +++ b/pkg/service/soundtouchweb/device_projection_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net/http" "net/http/httptest" + "strings" "sync" "testing" "time" @@ -69,6 +70,42 @@ func TestProjectDeviceEntriesCollapsesStereoPairUnderMaster(t *testing.T) { } } +// TestProjectDeviceEntriesNormalizesMemberRoleAndDeviceID guards against +// emitting raw, un-normalized Role/DeviceID into the frontend-facing JSON +// while validMasterGroup/registeredMembersAgree/sameGroupClaim all +// trim/uppercase those same fields for internal comparison. +func TestProjectDeviceEntriesNormalizesMemberRoleAndDeviceID(t *testing.T) { + group := &models.Group{ + ID: "pair-1", + Name: "Living Room + Living Room", + MasterDeviceID: "left-id", + Status: "GROUP_OK", + Roles: models.GroupRoles{Roles: []models.GroupRole{ + {DeviceID: " left-id ", Role: " left ", IPAddress: "192.0.2.10"}, + {DeviceID: " right-id ", Role: " right ", IPAddress: "192.0.2.11"}, + }}, + } + + got := projectDeviceEntries([]DeviceEntry{ + projectionDevice("192.0.2.10", "left-id", "Living Room", true, group), + projectionDevice("192.0.2.11", "right-id", "Living Room", true, group), + }) + + pair := got["192.0.2.10"].StereoPair + if pair == nil || len(pair.Members) != 2 { + t.Fatalf("expected a projected pair with two members: %+v", got) + } + + for _, member := range pair.Members { + if member.DeviceID != strings.TrimSpace(member.DeviceID) { + t.Errorf("member DeviceID = %q, want trimmed", member.DeviceID) + } + if member.Role != strings.ToUpper(member.Role) { + t.Errorf("member Role = %q, want upper-cased", member.Role) + } + } +} + func TestProjectDeviceEntriesShowsDegradedPairWhenMemberIsMissing(t *testing.T) { got := projectDeviceEntries([]DeviceEntry{ projectionDevice("192.0.2.10", "left-id", "Living Room", true, testStereoGroup()),