mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
Speakers echo back the source name as SourceAccount when no real credential is set (e.g. SourceAccount="TUNEIN" for a TUNEIN source). HandleDevicePlay was forwarding this verbatim, causing the speaker to try authenticating with the source name as a TuneIn account and returning INVALID_SOURCE. Clear SourceAccount when it equals Source; preserve it when it differs (real credentials such as Spotify or STORED_MUSIC UUIDs). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
707 lines
18 KiB
Go
707 lines
18 KiB
Go
// Package handlers contains tests for HTTP handlers.
|
|
package soundtouchweb
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gesellix/bose-soundtouch/pkg/client"
|
|
"github.com/gesellix/bose-soundtouch/pkg/models"
|
|
"github.com/gesellix/bose-soundtouch/pkg/service/soundtouchweb/webtypes"
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
func createTestApp() *WebApp {
|
|
app := NewWebApp()
|
|
|
|
// Add test device with minimal data
|
|
deviceInfo := &models.DeviceInfo{
|
|
Name: "Test Speaker",
|
|
Type: "SoundTouch 30",
|
|
NetworkInfo: []models.NetworkInfo{
|
|
{MacAddress: "TEST123", IPAddress: "192.0.2.100"},
|
|
},
|
|
}
|
|
|
|
device := webtypes.NewDeviceConnection(nil, deviceInfo)
|
|
device.SetStatus(&webtypes.DeviceStatus{
|
|
Volume: &models.Volume{ActualVolume: 50, MuteEnabled: false},
|
|
Bass: &models.Bass{ActualBass: 0},
|
|
IsConnected: true,
|
|
LastActivity: time.Now(),
|
|
})
|
|
|
|
app.AddDevice("test-device", device)
|
|
return app
|
|
}
|
|
|
|
func withChiParams(r *http.Request, params map[string]string) *http.Request {
|
|
rctx := chi.NewRouteContext()
|
|
for k, v := range params {
|
|
rctx.URLParams.Add(k, v)
|
|
}
|
|
return r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx))
|
|
}
|
|
|
|
func TestNewWebApp(t *testing.T) {
|
|
app := NewWebApp()
|
|
|
|
// Use require-style checks that satisfy static analyzer
|
|
if app == nil {
|
|
t.Fatal("NewWebApp returned nil")
|
|
}
|
|
|
|
if count := app.DeviceCount(); count != 0 {
|
|
t.Errorf("Expected empty device registry, got %d devices", count)
|
|
}
|
|
}
|
|
|
|
func TestHandleAPIDevices(t *testing.T) {
|
|
app := createTestApp()
|
|
|
|
req := httptest.NewRequest("GET", "/api/devices", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
app.HandleAPIDevices(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("Expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
contentType := w.Header().Get("Content-Type")
|
|
if !strings.Contains(contentType, "application/json") {
|
|
t.Errorf("Expected JSON content type, got %s", contentType)
|
|
}
|
|
|
|
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.Errorf("Expected success=true, got false")
|
|
}
|
|
|
|
// Check that devices data is present
|
|
data, ok := response.Data.(map[string]interface{})
|
|
if !ok {
|
|
t.Fatalf("Expected data to be map[string]interface{}")
|
|
}
|
|
|
|
if _, exists := data["test-device"]; !exists {
|
|
t.Errorf("Expected 'test-device' in response data")
|
|
}
|
|
}
|
|
|
|
func TestHandleAPIDevice(t *testing.T) {
|
|
app := createTestApp()
|
|
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
chiID string
|
|
expectedStatus int
|
|
expectSuccess bool
|
|
}{
|
|
{
|
|
name: "valid device",
|
|
path: "/api/device/test-device",
|
|
chiID: "test-device",
|
|
expectedStatus: http.StatusOK,
|
|
expectSuccess: true,
|
|
},
|
|
{
|
|
name: "missing device ID",
|
|
path: "/api/device/",
|
|
chiID: "",
|
|
expectedStatus: http.StatusBadRequest,
|
|
expectSuccess: false,
|
|
},
|
|
{
|
|
name: "unknown device",
|
|
path: "/api/device/unknown",
|
|
chiID: "unknown",
|
|
expectedStatus: http.StatusNotFound,
|
|
expectSuccess: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
req := httptest.NewRequest("GET", tt.path, nil)
|
|
if tt.chiID != "" {
|
|
req = withChiParams(req, map[string]string{"id": tt.chiID})
|
|
}
|
|
w := httptest.NewRecorder()
|
|
|
|
app.HandleAPIDevice(w, req)
|
|
|
|
if w.Code != tt.expectedStatus {
|
|
t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
|
|
}
|
|
|
|
contentType := w.Header().Get("Content-Type")
|
|
if !strings.Contains(contentType, "application/json") {
|
|
t.Errorf("Expected JSON content type, got %s", contentType)
|
|
}
|
|
|
|
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 != tt.expectSuccess {
|
|
t.Errorf("Expected success=%v, got %v", tt.expectSuccess, response.Success)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandleAPIControl_InvalidDevice(t *testing.T) {
|
|
app := createTestApp()
|
|
|
|
req := httptest.NewRequest("GET", "/api/control/unknown-device/play", nil)
|
|
req = withChiParams(req, map[string]string{"id": "unknown-device", "action": "play"})
|
|
w := httptest.NewRecorder()
|
|
|
|
app.HandleAPIControl(w, req)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Errorf("Expected status 404, got %d", 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.Errorf("Expected success=false, got true")
|
|
}
|
|
|
|
if response.Error != "Device not found" {
|
|
t.Errorf("Expected 'Device not found' error, got '%s'", response.Error)
|
|
}
|
|
}
|
|
|
|
func TestHandleAPIControl_InvalidPath(t *testing.T) {
|
|
app := createTestApp()
|
|
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
}{
|
|
{"missing action", "/api/control/test-device"},
|
|
{"missing device and action", "/api/control/"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
req := httptest.NewRequest("GET", tt.path, nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
app.HandleAPIControl(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("Expected status 400, got %d", 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.Errorf("Expected success=false, got true")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandleAPIControl_VolumeValidation(t *testing.T) {
|
|
app := createTestApp()
|
|
|
|
tests := []struct {
|
|
name string
|
|
method string
|
|
body string
|
|
expectedStatus int
|
|
expectSuccess bool
|
|
}{
|
|
{
|
|
name: "invalid method",
|
|
method: "GET",
|
|
body: "",
|
|
expectedStatus: http.StatusMethodNotAllowed,
|
|
expectSuccess: false,
|
|
},
|
|
{
|
|
name: "invalid JSON",
|
|
method: "POST",
|
|
body: `invalid json`,
|
|
expectedStatus: http.StatusBadRequest,
|
|
expectSuccess: false,
|
|
},
|
|
{
|
|
name: "volume too low",
|
|
method: "POST",
|
|
body: `{"level": -1}`,
|
|
expectedStatus: http.StatusBadRequest,
|
|
expectSuccess: false,
|
|
},
|
|
{
|
|
name: "volume too high",
|
|
method: "POST",
|
|
body: `{"level": 101}`,
|
|
expectedStatus: http.StatusBadRequest,
|
|
expectSuccess: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var req *http.Request
|
|
if tt.body != "" {
|
|
req = httptest.NewRequest(tt.method, "/api/control/test-device/volume", strings.NewReader(tt.body))
|
|
} else {
|
|
req = httptest.NewRequest(tt.method, "/api/control/test-device/volume", nil)
|
|
}
|
|
req = withChiParams(req, map[string]string{"id": "test-device", "action": "volume"})
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
app.HandleAPIControl(w, req)
|
|
|
|
if w.Code != tt.expectedStatus {
|
|
t.Errorf("Expected status %d, got %d", tt.expectedStatus, 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 != tt.expectSuccess {
|
|
t.Errorf("Expected success=%v, got %v", tt.expectSuccess, response.Success)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandleAPIControl_BassValidation(t *testing.T) {
|
|
app := createTestApp()
|
|
|
|
tests := []struct {
|
|
name string
|
|
method string
|
|
body string
|
|
expectedStatus int
|
|
expectSuccess bool
|
|
}{
|
|
{
|
|
name: "bass too low",
|
|
method: "POST",
|
|
body: `{"level": -10}`,
|
|
expectedStatus: http.StatusBadRequest,
|
|
expectSuccess: false,
|
|
},
|
|
{
|
|
name: "bass too high",
|
|
method: "POST",
|
|
body: `{"level": 10}`,
|
|
expectedStatus: http.StatusBadRequest,
|
|
expectSuccess: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
req := httptest.NewRequest(tt.method, "/api/control/test-device/bass", strings.NewReader(tt.body))
|
|
req = withChiParams(req, map[string]string{"id": "test-device", "action": "bass"})
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
app.HandleAPIControl(w, req)
|
|
|
|
if w.Code != tt.expectedStatus {
|
|
t.Errorf("Expected status %d, got %d", tt.expectedStatus, 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 != tt.expectSuccess {
|
|
t.Errorf("Expected success=%v, got %v", tt.expectSuccess, response.Success)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandleAPIControl_PresetValidation(t *testing.T) {
|
|
app := createTestApp()
|
|
|
|
tests := []struct {
|
|
name string
|
|
query string
|
|
expectedStatus int
|
|
expectSuccess bool
|
|
}{
|
|
{
|
|
name: "missing preset ID",
|
|
query: "",
|
|
expectedStatus: http.StatusBadRequest,
|
|
expectSuccess: false,
|
|
},
|
|
{
|
|
name: "invalid preset ID",
|
|
query: "?id=abc",
|
|
expectedStatus: http.StatusBadRequest,
|
|
expectSuccess: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/api/control/test-device/preset"+tt.query, nil)
|
|
req = withChiParams(req, map[string]string{"id": "test-device", "action": "preset"})
|
|
w := httptest.NewRecorder()
|
|
|
|
app.HandleAPIControl(w, req)
|
|
|
|
if w.Code != tt.expectedStatus {
|
|
t.Errorf("Expected status %d, got %d", tt.expectedStatus, 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 != tt.expectSuccess {
|
|
t.Errorf("Expected success=%v, got %v", tt.expectSuccess, response.Success)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandleAPIControl_SourceValidation(t *testing.T) {
|
|
app := createTestApp()
|
|
|
|
req := httptest.NewRequest("GET", "/api/control/test-device/source", nil)
|
|
req = withChiParams(req, map[string]string{"id": "test-device", "action": "source"})
|
|
w := httptest.NewRecorder()
|
|
|
|
app.HandleAPIControl(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("Expected status 400, got %d", 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.Errorf("Expected success=false, got true")
|
|
}
|
|
|
|
if response.Error != "Source name required" {
|
|
t.Errorf("Expected 'Source name required' error, got '%s'", response.Error)
|
|
}
|
|
}
|
|
|
|
func TestHandleAPIDiscover(t *testing.T) {
|
|
app := createTestApp()
|
|
|
|
tests := []struct {
|
|
name string
|
|
method string
|
|
expectedStatus int
|
|
expectSuccess bool
|
|
}{
|
|
{
|
|
name: "valid POST request",
|
|
method: "POST",
|
|
expectedStatus: http.StatusOK,
|
|
expectSuccess: true,
|
|
},
|
|
{
|
|
name: "invalid GET request",
|
|
method: "GET",
|
|
expectedStatus: http.StatusMethodNotAllowed,
|
|
expectSuccess: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
req := httptest.NewRequest(tt.method, "/api/discover", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
app.HandleAPIDiscover(w, req)
|
|
|
|
if w.Code != tt.expectedStatus {
|
|
t.Errorf("Expected status %d, got %d", tt.expectedStatus, 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 != tt.expectSuccess {
|
|
t.Errorf("Expected success=%v, got %v", tt.expectSuccess, response.Success)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSendError(t *testing.T) {
|
|
app := createTestApp()
|
|
|
|
w := httptest.NewRecorder()
|
|
app.sendError(w, "Test error", http.StatusBadRequest)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("Expected status 400, got %d", 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.Errorf("Expected success=false, got true")
|
|
}
|
|
|
|
if response.Error != "Test error" {
|
|
t.Errorf("Expected 'Test error', got '%s'", response.Error)
|
|
}
|
|
|
|
contentType := w.Header().Get("Content-Type")
|
|
if contentType != "application/json" {
|
|
t.Errorf("Expected Content-Type 'application/json', got '%s'", contentType)
|
|
}
|
|
}
|
|
|
|
func TestHandleWebSocket_InvalidUpgrade(t *testing.T) {
|
|
app := createTestApp()
|
|
|
|
// Test without proper WebSocket headers (should fail gracefully)
|
|
req := httptest.NewRequest("GET", "/ws", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
// This will fail because it's not a real WebSocket upgrade, but should not panic
|
|
app.HandleWebSocket(w, req)
|
|
|
|
// We're just checking that the handler doesn't panic
|
|
// The actual upgrade will fail in test environment without proper headers
|
|
}
|
|
|
|
func TestHandleAPIControl_UnsupportedAction(t *testing.T) {
|
|
app := createTestApp()
|
|
|
|
req := httptest.NewRequest("GET", "/api/control/test-device/unsupported", nil)
|
|
req = withChiParams(req, map[string]string{"id": "test-device", "action": "unsupported"})
|
|
w := httptest.NewRecorder()
|
|
|
|
app.HandleAPIControl(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("Expected status 400, got %d", 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.Errorf("Expected success=false, got true")
|
|
}
|
|
|
|
if response.Error != "Unknown action" {
|
|
t.Errorf("Expected 'Unknown action' error, got '%s'", response.Error)
|
|
}
|
|
}
|
|
|
|
func TestHandleAPIVersion(t *testing.T) {
|
|
app := createTestApp()
|
|
app.Version = "1.2.3"
|
|
app.Commit = "abcdef123"
|
|
app.Date = "2023-01-01"
|
|
app.RepoURL = "https://github.com/example/repo"
|
|
|
|
req := httptest.NewRequest("GET", "/api/version", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
app.HandleAPIVersion(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("Expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
var resp webtypes.APIResponse
|
|
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("Failed to decode response: %v", err)
|
|
}
|
|
|
|
if !resp.Success {
|
|
t.Errorf("Expected success=true, got false")
|
|
}
|
|
|
|
data, ok := resp.Data.(map[string]interface{})
|
|
if !ok {
|
|
t.Fatalf("Expected data to be map[string]interface{}, got %T", resp.Data)
|
|
}
|
|
|
|
expected := map[string]string{
|
|
"version": "1.2.3",
|
|
"commit": "abcdef123",
|
|
"date": "2023-01-01",
|
|
"repo_url": "https://github.com/example/repo",
|
|
"release_url": "https://github.com/example/repo/releases/tag/1.2.3",
|
|
"commit_url": "https://github.com/example/repo/commit/abcdef123",
|
|
}
|
|
|
|
for k, v := range expected {
|
|
if data[k] != v {
|
|
t.Errorf("Expected %s=%s, got %v", k, v, data[k])
|
|
}
|
|
}
|
|
}
|
|
|
|
// Benchmark tests
|
|
func BenchmarkHandleAPIDevices(b *testing.B) {
|
|
app := createTestApp()
|
|
|
|
// Add more devices for realistic benchmarking
|
|
for i := 0; i < 10; i++ {
|
|
deviceID := "device-" + string(rune('0'+i))
|
|
conn := webtypes.NewDeviceConnection(&client.Client{}, &models.DeviceInfo{Name: "Test Device " + deviceID})
|
|
conn.SetStatus(&webtypes.DeviceStatus{IsConnected: true})
|
|
app.AddDevice(deviceID, conn)
|
|
}
|
|
|
|
req := httptest.NewRequest("GET", "/api/devices", nil)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
w := httptest.NewRecorder()
|
|
app.HandleAPIDevices(w, req)
|
|
}
|
|
}
|
|
|
|
func BenchmarkHandleAPIDevice(b *testing.B) {
|
|
app := createTestApp()
|
|
req := httptest.NewRequest("GET", "/api/device/test-device", nil)
|
|
req = withChiParams(req, map[string]string{"id": "test-device"})
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
w := httptest.NewRecorder()
|
|
app.HandleAPIDevice(w, req)
|
|
}
|
|
}
|
|
|
|
func BenchmarkSendError(b *testing.B) {
|
|
app := createTestApp()
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
w := httptest.NewRecorder()
|
|
app.sendError(w, "Test error", http.StatusBadRequest)
|
|
}
|
|
}
|
|
|
|
// TestHandleDevicePlay_SourceAccountFiltering verifies that a SourceAccount
|
|
// equal to Source (the placeholder speakers echo back, e.g. "TUNEIN") is
|
|
// stripped before the ContentItem XML is sent to the speaker, while a real
|
|
// credential (SourceAccount != Source) is preserved.
|
|
func TestHandleDevicePlay_SourceAccountFiltering(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
source string
|
|
sourceAccount string
|
|
wantSourceAccount string // empty means the XML attr must be absent
|
|
}{
|
|
{
|
|
name: "placeholder echoed back — stripped",
|
|
source: "TUNEIN",
|
|
sourceAccount: "TUNEIN",
|
|
wantSourceAccount: "",
|
|
},
|
|
{
|
|
name: "real credential — preserved",
|
|
source: "TUNEIN",
|
|
sourceAccount: "real-account-id",
|
|
wantSourceAccount: "real-account-id",
|
|
},
|
|
{
|
|
name: "empty account — stays empty",
|
|
source: "TUNEIN",
|
|
sourceAccount: "",
|
|
wantSourceAccount: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var capturedBody string
|
|
|
|
// Fake speaker that captures the /select POST body.
|
|
speaker := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/select" {
|
|
b, _ := io.ReadAll(r.Body)
|
|
capturedBody = string(b)
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer speaker.Close()
|
|
|
|
speakerClient := client.NewClient(&client.Config{Host: speaker.URL})
|
|
|
|
app := NewWebApp()
|
|
deviceInfo := &models.DeviceInfo{Name: "Test Speaker"}
|
|
conn := webtypes.NewDeviceConnection(speakerClient, deviceInfo)
|
|
conn.SetStatus(&webtypes.DeviceStatus{IsConnected: true, LastActivity: time.Now()})
|
|
app.AddDevice("play-device", conn)
|
|
|
|
body := strings.NewReader(`{
|
|
"source":"` + tt.source + `",
|
|
"type":"stationurl",
|
|
"location":"/v1/playback/station/s6634",
|
|
"sourceAccount":"` + tt.sourceAccount + `",
|
|
"itemName":"Venice Classic Radio"
|
|
}`)
|
|
req := httptest.NewRequest("POST", "/api/device-play/play-device", body)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req = withChiParams(req, map[string]string{"id": "play-device"})
|
|
w := httptest.NewRecorder()
|
|
|
|
app.HandleDevicePlay(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
// SourceAccount XML attribute is always emitted (no omitempty on the struct
|
|
// tag), so check its value rather than its presence/absence.
|
|
want := `sourceAccount="` + tt.wantSourceAccount + `"`
|
|
if !strings.Contains(capturedBody, want) {
|
|
t.Errorf("XML should contain %q, got: %s", want, capturedBody)
|
|
}
|
|
})
|
|
}
|
|
}
|