fix(player): normalize stereo-pair member Role/DeviceID in JSON

newStereoPairView emitted raw, un-normalized role.DeviceID/role.Role
while validMasterGroup/registeredMembersAgree/sameGroupClaim trim and
uppercase those same fields for internal comparison. Normalize before
assigning so a future frontend feature reading member.Role/.DeviceID
directly doesn't need to re-normalize it itself.

Found in code review of PR #665 (finding #8).
This commit is contained in:
Tobias Gesellchen
2026-09-04 21:13:44 +02:00
parent fd62f6fbeb
commit d0e8194ab5
2 changed files with 39 additions and 2 deletions
@@ -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,
}
@@ -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()),