mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-09-07 15:07:17 +00:00
fix(player): route HandleAPIDevice through stereo-pair projection
The singular GET /api/control/devices/{id} bypassed the projection that
HandleAPIDevices and both WebSocket frames already apply. A hidden
stereo-pair member was absent from the list but still fully fetchable,
unprojected, by its own id. Add deviceViewForID and return 404 for a
hidden member's own id, consistent with it already being absent from
the list.
Found in code review of PR #665 (finding #3).
This commit is contained in:
@@ -76,6 +76,16 @@ func (app *WebApp) deviceViewSnapshot() map[string]deviceView {
|
||||
return projectDeviceEntries(app.DeviceSnapshot())
|
||||
}
|
||||
|
||||
// deviceViewForID projects the registry into a single logical control
|
||||
// target and reports whether id is currently visible in the player-facing
|
||||
// inventory. A hidden stereo-pair member is not visible under its own id --
|
||||
// only its pair's master key exposes it, via StereoPair.Members.
|
||||
func (app *WebApp) deviceViewForID(id string) (deviceView, bool) {
|
||||
view, ok := app.deviceViewSnapshot()[id]
|
||||
|
||||
return view, ok
|
||||
}
|
||||
|
||||
func projectDeviceEntries(snapshot []DeviceEntry) map[string]deviceView {
|
||||
return projectCapturedDeviceEntries(captureDeviceProjectionEntries(snapshot))
|
||||
}
|
||||
|
||||
@@ -294,14 +294,21 @@ func (app *WebApp) HandleAPIDevice(w http.ResponseWriter, r *http.Request) {
|
||||
go app.ConnectDeviceWebSocket(deviceID, device)
|
||||
}
|
||||
|
||||
// Route through the same stereo-pair projection as HandleAPIDevices and
|
||||
// the WebSocket frames. A hidden pair member is exactly as unaddressable
|
||||
// here as it is from the list -- otherwise it would be absent from
|
||||
// "devices" but still fully fetchable, unprojected, by its own id.
|
||||
view, visible := app.deviceViewForID(deviceID)
|
||||
if !visible {
|
||||
app.sendError(w, "Device not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
response := webtypes.APIResponse{
|
||||
Success: true,
|
||||
Data: map[string]interface{}{
|
||||
"info": device.DeviceInfo,
|
||||
"status": device.Status(),
|
||||
},
|
||||
Data: view,
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
|
||||
@@ -163,6 +163,63 @@ func TestHandleAPIDevice(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleAPIDevice_MasterIncludesStereoPairProjection(t *testing.T) {
|
||||
app := NewWebApp()
|
||||
group := testStereoGroup()
|
||||
app.AddDevice("192.0.2.10", projectionDevice("192.0.2.10", "left-id", "Living Room", true, group).Device)
|
||||
app.AddDevice("192.0.2.11", projectionDevice("192.0.2.11", "right-id", "Living Room", true, group).Device)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/control/devices/192.0.2.10", nil)
|
||||
req = withChiParams(req, map[string]string{"id": "192.0.2.10"})
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
app.HandleAPIDevice(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("Expected status %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
var response webtypes.APIResponse
|
||||
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
|
||||
t.Fatalf("Failed to decode response: %v", err)
|
||||
}
|
||||
|
||||
data, ok := response.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("response.Data = %#v, want a map", response.Data)
|
||||
}
|
||||
|
||||
if _, ok := data["stereoPair"]; !ok {
|
||||
t.Fatalf("response.Data = %#v, want a stereoPair projection for the pair master", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleAPIDevice_HiddenPairMemberNotFound(t *testing.T) {
|
||||
app := NewWebApp()
|
||||
group := testStereoGroup()
|
||||
app.AddDevice("192.0.2.10", projectionDevice("192.0.2.10", "left-id", "Living Room", true, group).Device)
|
||||
app.AddDevice("192.0.2.11", projectionDevice("192.0.2.11", "right-id", "Living Room", true, group).Device)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/control/devices/192.0.2.11", nil)
|
||||
req = withChiParams(req, map[string]string{"id": "192.0.2.11"})
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
app.HandleAPIDevice(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Fatalf("Expected status %d for a hidden pair member's own id, got %d", http.StatusNotFound, w.Code)
|
||||
}
|
||||
|
||||
var response webtypes.APIResponse
|
||||
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
|
||||
t.Fatalf("Failed to decode response: %v", err)
|
||||
}
|
||||
|
||||
if response.Success {
|
||||
t.Fatal("Expected success=false for a hidden pair member's own id")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleAPIControl_InvalidDevice(t *testing.T) {
|
||||
app := createTestApp()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user