mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-19 00:56:16 +00:00
Major Features: • POST /key endpoint implementation with XML model and validation • Comprehensive media control commands (play, pause, stop, volume, presets) • Automatic host:port parsing in CLI for improved UX • Production-ready with full test coverage Key Control Implementation: • Add Key model with XML marshaling and validation (pkg/models/key.go) • Support all standard keys: PLAY, PAUSE, STOP, PREV_TRACK, NEXT_TRACK, VOLUME_UP/DOWN, PRESET_1-6 • Client methods: SendKey(), Play(), Pause(), Stop(), VolumeUp(), VolumeDown(), SelectPreset() • CLI commands: -play, -pause, -stop, -next, -prev, -volume-up, -volume-down, -preset, -key • Critical fix: Use 'Gabbo' as sender (only accepted value by SoundTouch API) Host:Port Parsing Enhancement: • Support -host 192.168.178.28:8090 format in addition to separate -host/-port flags • Robust parsing with IPv4, IPv6, and hostname support • Graceful fallback for invalid input • Backward compatible with existing usage Testing & Documentation: • Comprehensive unit tests for key functionality and host:port parsing • Integration tested with real SoundTouch 10 and SoundTouch 20 devices • Complete documentation in docs/KEY-CONTROLS.md and docs/HOST-PORT-PARSING.md • All tests pass, no diagnostics errors Breaking Changes: None Backward Compatibility: Fully maintained Tested with: • SoundTouch 10 (192.168.178.28:8090) ✅ • SoundTouch 20 (192.168.178.35:8090) ✅
287 lines
6.1 KiB
Go
287 lines
6.1 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"testing"
|
|
)
|
|
|
|
func TestNewKey(t *testing.T) {
|
|
key := NewKey(KeyPlay)
|
|
|
|
if key.State != KeyStatePress {
|
|
t.Errorf("Expected state %q, got %q", KeyStatePress, key.State)
|
|
}
|
|
|
|
if key.Sender != "Gabbo" {
|
|
t.Errorf("Expected sender %q, got %q", "Gabbo", key.Sender)
|
|
}
|
|
|
|
if key.Value != KeyPlay {
|
|
t.Errorf("Expected value %q, got %q", KeyPlay, key.Value)
|
|
}
|
|
}
|
|
|
|
func TestNewKeyPress(t *testing.T) {
|
|
key := NewKeyPress(KeyPause)
|
|
|
|
if key.State != KeyStatePress {
|
|
t.Errorf("Expected state %q, got %q", KeyStatePress, key.State)
|
|
}
|
|
|
|
if key.Value != KeyPause {
|
|
t.Errorf("Expected value %q, got %q", KeyPause, key.Value)
|
|
}
|
|
}
|
|
|
|
func TestNewKeyRelease(t *testing.T) {
|
|
key := NewKeyRelease(KeyStop)
|
|
|
|
if key.State != KeyStateRelease {
|
|
t.Errorf("Expected state %q, got %q", KeyStateRelease, key.State)
|
|
}
|
|
|
|
if key.Sender != "Gabbo" {
|
|
t.Errorf("Expected sender %q, got %q", "Gabbo", key.Sender)
|
|
}
|
|
|
|
if key.Value != KeyStop {
|
|
t.Errorf("Expected value %q, got %q", KeyStop, key.Value)
|
|
}
|
|
}
|
|
|
|
func TestKeyXMLMarshal(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
key *Key
|
|
expectedXML string
|
|
}{
|
|
{
|
|
name: "PLAY key press",
|
|
key: NewKey(KeyPlay),
|
|
expectedXML: `<key state="press" sender="Gabbo">PLAY</key>`,
|
|
},
|
|
{
|
|
name: "PAUSE key press",
|
|
key: NewKey(KeyPause),
|
|
expectedXML: `<key state="press" sender="Gabbo">PAUSE</key>`,
|
|
},
|
|
{
|
|
name: "STOP key release",
|
|
key: NewKeyRelease(KeyStop),
|
|
expectedXML: `<key state="release" sender="Gabbo">STOP</key>`,
|
|
},
|
|
{
|
|
name: "VOLUME_UP key press",
|
|
key: NewKey(KeyVolumeUp),
|
|
expectedXML: `<key state="press" sender="Gabbo">VOLUME_UP</key>`,
|
|
},
|
|
{
|
|
name: "PRESET_1 key press",
|
|
key: NewKey(KeyPreset1),
|
|
expectedXML: `<key state="press" sender="Gabbo">PRESET_1</key>`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
xmlData, err := xml.Marshal(tt.key)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal XML: %v", err)
|
|
}
|
|
|
|
if string(xmlData) != tt.expectedXML {
|
|
t.Errorf("Expected XML %q, got %q", tt.expectedXML, string(xmlData))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestKeyXMLUnmarshal(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
xmlData string
|
|
want Key
|
|
}{
|
|
{
|
|
name: "PLAY key press",
|
|
xmlData: `<key state="press" sender="Gabbo">PLAY</key>`,
|
|
want: Key{
|
|
State: KeyStatePress,
|
|
Sender: "Gabbo",
|
|
Value: KeyPlay,
|
|
},
|
|
},
|
|
{
|
|
name: "PAUSE key release",
|
|
xmlData: `<key state="release" sender="TestSender">PAUSE</key>`,
|
|
want: Key{
|
|
State: KeyStateRelease,
|
|
Sender: "TestSender",
|
|
Value: KeyPause,
|
|
},
|
|
},
|
|
{
|
|
name: "VOLUME_DOWN key press",
|
|
xmlData: `<key state="press" sender="WebApp">VOLUME_DOWN</key>`,
|
|
want: Key{
|
|
State: KeyStatePress,
|
|
Sender: "WebApp",
|
|
Value: KeyVolumeDown,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var key Key
|
|
err := xml.Unmarshal([]byte(tt.xmlData), &key)
|
|
if err != nil {
|
|
t.Fatalf("Failed to unmarshal XML: %v", err)
|
|
}
|
|
|
|
if key.State != tt.want.State {
|
|
t.Errorf("Expected state %q, got %q", tt.want.State, key.State)
|
|
}
|
|
|
|
if key.Sender != tt.want.Sender {
|
|
t.Errorf("Expected sender %q, got %q", tt.want.Sender, key.Sender)
|
|
}
|
|
|
|
if key.Value != tt.want.Value {
|
|
t.Errorf("Expected value %q, got %q", tt.want.Value, key.Value)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsValidKey(t *testing.T) {
|
|
validKeys := []string{
|
|
KeyPlay, KeyPause, KeyStop,
|
|
KeyPrevTrack, KeyNextTrack,
|
|
KeyVolumeUp, KeyVolumeDown,
|
|
KeyPreset1, KeyPreset2, KeyPreset3,
|
|
KeyPreset4, KeyPreset5, KeyPreset6,
|
|
}
|
|
|
|
invalidKeys := []string{
|
|
"INVALID_KEY", "play", "PAUSE_BUTTON",
|
|
"PRESET_7", "PRESET_0", "", "VOLUME",
|
|
}
|
|
|
|
for _, key := range validKeys {
|
|
t.Run("valid_"+key, func(t *testing.T) {
|
|
if !IsValidKey(key) {
|
|
t.Errorf("Expected %q to be valid", key)
|
|
}
|
|
})
|
|
}
|
|
|
|
for _, key := range invalidKeys {
|
|
t.Run("invalid_"+key, func(t *testing.T) {
|
|
if IsValidKey(key) {
|
|
t.Errorf("Expected %q to be invalid", key)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetAllValidKeys(t *testing.T) {
|
|
keys := GetAllValidKeys()
|
|
|
|
expectedKeys := []string{
|
|
KeyPlay, KeyPause, KeyStop,
|
|
KeyPrevTrack, KeyNextTrack,
|
|
KeyVolumeUp, KeyVolumeDown,
|
|
KeyPreset1, KeyPreset2, KeyPreset3,
|
|
KeyPreset4, KeyPreset5, KeyPreset6,
|
|
}
|
|
|
|
if len(keys) != len(expectedKeys) {
|
|
t.Errorf("Expected %d keys, got %d", len(expectedKeys), len(keys))
|
|
}
|
|
|
|
keyMap := make(map[string]bool)
|
|
for _, key := range keys {
|
|
keyMap[key] = true
|
|
}
|
|
|
|
for _, expectedKey := range expectedKeys {
|
|
if !keyMap[expectedKey] {
|
|
t.Errorf("Expected key %q not found in result", expectedKey)
|
|
}
|
|
}
|
|
|
|
// Verify all returned keys are valid
|
|
for _, key := range keys {
|
|
if !IsValidKey(key) {
|
|
t.Errorf("GetAllValidKeys returned invalid key: %q", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestKeyConstants(t *testing.T) {
|
|
// Test that all key constants are defined and non-empty
|
|
keyTests := []struct {
|
|
name string
|
|
value string
|
|
}{
|
|
{"KeyPlay", KeyPlay},
|
|
{"KeyPause", KeyPause},
|
|
{"KeyStop", KeyStop},
|
|
{"KeyPrevTrack", KeyPrevTrack},
|
|
{"KeyNextTrack", KeyNextTrack},
|
|
{"KeyVolumeUp", KeyVolumeUp},
|
|
{"KeyVolumeDown", KeyVolumeDown},
|
|
{"KeyPreset1", KeyPreset1},
|
|
{"KeyPreset2", KeyPreset2},
|
|
{"KeyPreset3", KeyPreset3},
|
|
{"KeyPreset4", KeyPreset4},
|
|
{"KeyPreset5", KeyPreset5},
|
|
{"KeyPreset6", KeyPreset6},
|
|
}
|
|
|
|
for _, tt := range keyTests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.value == "" {
|
|
t.Errorf("Expected %s to be non-empty", tt.name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestKeyStateConstants(t *testing.T) {
|
|
if KeyStatePress == "" {
|
|
t.Error("KeyStatePress should not be empty")
|
|
}
|
|
|
|
if KeyStateRelease == "" {
|
|
t.Error("KeyStateRelease should not be empty")
|
|
}
|
|
|
|
if KeyStatePress == KeyStateRelease {
|
|
t.Error("KeyStatePress and KeyStateRelease should be different")
|
|
}
|
|
}
|
|
|
|
// Benchmark tests
|
|
func BenchmarkNewKey(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
NewKey(KeyPlay)
|
|
}
|
|
}
|
|
|
|
func BenchmarkKeyXMLMarshal(b *testing.B) {
|
|
key := NewKey(KeyPlay)
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
xml.Marshal(key)
|
|
}
|
|
}
|
|
|
|
func BenchmarkIsValidKey(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
IsValidKey(KeyPlay)
|
|
}
|
|
}
|