diff --git a/cmd/soundtouch-cli/cmd_audio.go b/cmd/soundtouch-cli/cmd_audio.go index b5fb4dd..f126fcd 100644 --- a/cmd/soundtouch-cli/cmd_audio.go +++ b/cmd/soundtouch-cli/cmd_audio.go @@ -62,9 +62,11 @@ func setAudioDSPControls(c *cli.Context) error { } fmt.Println("āœ… DSP controls updated successfully") + if audioMode != "" { fmt.Printf(" Audio Mode: %s\n", audioMode) } + if videoSyncDelay != 0 { fmt.Printf(" Video Sync Delay: %d ms\n", videoSyncDelay) } @@ -161,21 +163,24 @@ func setAudioToneControls(c *cli.Context) error { } var bass, treble *int + var err error if bassStr != "" { - bassVal, err := strconv.Atoi(bassStr) - if err != nil { + bassVal, errVal := strconv.Atoi(bassStr) + if errVal != nil { return fmt.Errorf("invalid bass value: %s", bassStr) } + bass = &bassVal } if trebleStr != "" { - trebleVal, err := strconv.Atoi(trebleStr) - if err != nil { + trebleVal, errVal := strconv.Atoi(trebleStr) + if errVal != nil { return fmt.Errorf("invalid treble value: %s", trebleStr) } + treble = &trebleVal } @@ -194,9 +199,11 @@ func setAudioToneControls(c *cli.Context) error { } fmt.Println("āœ… Advanced tone controls updated successfully") + if bass != nil { fmt.Printf(" Bass: %d\n", *bass) } + if treble != nil { fmt.Printf(" Treble: %d\n", *treble) } @@ -295,21 +302,24 @@ func setAudioLevelControls(c *cli.Context) error { } var frontCenter, rearSurround *int + var err error if frontCenterStr != "" { - frontCenterVal, err := strconv.Atoi(frontCenterStr) - if err != nil { + frontCenterVal, errVal := strconv.Atoi(frontCenterStr) + if errVal != nil { return fmt.Errorf("invalid front-center value: %s", frontCenterStr) } + frontCenter = &frontCenterVal } if rearSurroundStr != "" { - rearSurroundVal, err := strconv.Atoi(rearSurroundStr) - if err != nil { + rearSurroundVal, errVal := strconv.Atoi(rearSurroundStr) + if errVal != nil { return fmt.Errorf("invalid rear-surround value: %s", rearSurroundStr) } + rearSurround = &rearSurroundVal } @@ -328,9 +338,11 @@ func setAudioLevelControls(c *cli.Context) error { } fmt.Println("āœ… Speaker level controls updated successfully") + if frontCenter != nil { fmt.Printf(" Front-Center Speaker: %d\n", *frontCenter) } + if rearSurround != nil { fmt.Printf(" Rear-Surround Speakers: %d\n", *rearSurround) } diff --git a/examples/advanced-audio-controls.go b/examples/advanced-audio-controls/main.go similarity index 58% rename from examples/advanced-audio-controls.go rename to examples/advanced-audio-controls/main.go index 4c0cdb7..7d8eb9c 100644 --- a/examples/advanced-audio-controls.go +++ b/examples/advanced-audio-controls/main.go @@ -1,3 +1,4 @@ +// Package main provides an example of using advanced audio controls. package main import ( @@ -19,7 +20,37 @@ func main() { fmt.Println("=================================================") // Example 1: Check device capabilities first + checkCapabilities(soundtouchClient) + + // Example 2: DSP Audio Controls + demonstrateDSPControls(soundtouchClient) + + time.Sleep(2 * time.Second) + + // Example 3: Advanced Tone Controls (Bass/Treble) + demonstrateToneControls(soundtouchClient) + + time.Sleep(2 * time.Second) + + // Example 4: Speaker Level Controls + demonstrateLevelControls(soundtouchClient) + + // Example 5: Compare with basic controls + demonstrateBasicControls(soundtouchClient) + + // Example 6: Error handling and validation + demonstrateErrorHandling(soundtouchClient) + + // Example 7: CLI command equivalents + showCLIEquivalents(deviceIP) + + fmt.Println("\nšŸŽ‰ Advanced audio controls example completed!") + printNotes() +} + +func checkCapabilities(soundtouchClient *client.Client) { fmt.Println("\n1. Checking device capabilities...") + capabilities, err := soundtouchClient.GetCapabilities() if err != nil { log.Printf("āŒ Failed to get capabilities: %v", err) @@ -27,7 +58,6 @@ func main() { } fmt.Printf("šŸ“‹ Device: %s\n", capabilities.DeviceID) - fmt.Printf(" Type: %s\n", capabilities.Type) // Look for advanced audio capabilities in the response // (Note: Advanced audio controls are only available on professional/high-end devices) @@ -35,160 +65,174 @@ func main() { fmt.Println(" - DSP Controls: Check device response for 'audiodspcontrols'") fmt.Println(" - Tone Controls: Check device response for 'audioproducttonecontrols'") fmt.Println(" - Level Controls: Check device response for 'audioproductlevelcontrols'") +} - // Example 2: DSP Audio Controls +func demonstrateDSPControls(soundtouchClient *client.Client) { fmt.Println("\n2. DSP Audio Controls...") dspControls, err := soundtouchClient.GetAudioDSPControls() if err != nil { log.Printf("āš ļø DSP controls not available on this device: %v", err) fmt.Println(" This is normal for consumer-grade SoundTouch devices") - } else { - fmt.Printf("šŸŽ›ļø Current DSP Settings: %s\n", dspControls.String()) - // Try setting a different audio mode - supportedModes := dspControls.GetSupportedAudioModes() - if len(supportedModes) > 0 { - newMode := supportedModes[0] - if newMode != dspControls.AudioMode && newMode != "" { - fmt.Printf(" Changing audio mode to: %s\n", newMode) + return + } - err = soundtouchClient.SetAudioMode(newMode) - if err != nil { - log.Printf("āŒ Failed to set audio mode: %v", err) - } else { - fmt.Printf("āœ… Audio mode changed successfully\n") - } - } - } + fmt.Printf("šŸŽ›ļø Current DSP Settings: %s\n", dspControls.String()) - // Demonstrate video sync delay adjustment - if dspControls.VideoSyncAudioDelay != 50 { - fmt.Println(" Setting video sync audio delay to 50ms...") - err = soundtouchClient.SetVideoSyncAudioDelay(50) + // Try setting a different audio mode + supportedModes := dspControls.GetSupportedAudioModes() + if len(supportedModes) > 0 { + newMode := supportedModes[0] + if newMode != dspControls.AudioMode && newMode != "" { + fmt.Printf(" Changing audio mode to: %s\n", newMode) + + err = soundtouchClient.SetAudioMode(newMode) if err != nil { - log.Printf("āŒ Failed to set video sync delay: %v", err) + log.Printf("āŒ Failed to set audio mode: %v", err) } else { - fmt.Printf("āœ… Video sync delay adjusted\n") + fmt.Printf("āœ… Audio mode changed successfully\n") } } - - // Combined DSP settings update - fmt.Println(" Updating DSP controls (mode + delay)...") - err = soundtouchClient.SetAudioDSPControls("NORMAL", 25) - if err != nil { - log.Printf("āŒ Failed to set DSP controls: %v", err) - } else { - fmt.Printf("āœ… DSP controls updated\n") - } } - time.Sleep(2 * time.Second) + // Demonstrate video sync delay adjustment + if dspControls.VideoSyncAudioDelay != 50 { + fmt.Println(" Setting video sync audio delay to 50ms...") - // Example 3: Advanced Tone Controls (Bass/Treble) + err = soundtouchClient.SetVideoSyncAudioDelay(50) + if err != nil { + log.Printf("āŒ Failed to set video sync delay: %v", err) + } else { + fmt.Printf("āœ… Video sync delay adjusted\n") + } + } + + // Combined DSP settings update + fmt.Println(" Updating DSP controls (mode + delay)...") + + err = soundtouchClient.SetAudioDSPControls("NORMAL", 25) + if err != nil { + log.Printf("āŒ Failed to set DSP controls: %v", err) + } else { + fmt.Printf("āœ… DSP controls updated\n") + } +} + +func demonstrateToneControls(soundtouchClient *client.Client) { fmt.Println("\n3. Advanced Tone Controls...") toneControls, err := soundtouchClient.GetAudioProductToneControls() if err != nil { log.Printf("āš ļø Advanced tone controls not available on this device: %v", err) fmt.Println(" Use the basic bass control instead (soundtouch-cli bass)") - } else { - fmt.Printf("šŸŽšļø Current Tone Settings: %s\n", toneControls.String()) - // Adjust bass only - newBassLevel := 3 - if toneControls.Bass.Value != newBassLevel { - fmt.Printf(" Setting advanced bass to %d...\n", newBassLevel) - err = soundtouchClient.SetAdvancedBass(newBassLevel) - if err != nil { - log.Printf("āŒ Failed to set advanced bass: %v", err) - } else { - fmt.Printf("āœ… Advanced bass adjusted\n") - } - } + return + } - time.Sleep(1 * time.Second) + fmt.Printf("šŸŽšļø Current Tone Settings: %s\n", toneControls.String()) - // Adjust treble only - newTrebleLevel := -1 - if toneControls.Treble.Value != newTrebleLevel { - fmt.Printf(" Setting advanced treble to %d...\n", newTrebleLevel) - err = soundtouchClient.SetAdvancedTreble(newTrebleLevel) - if err != nil { - log.Printf("āŒ Failed to set advanced treble: %v", err) - } else { - fmt.Printf("āœ… Advanced treble adjusted\n") - } - } + // Adjust bass only + newBassLevel := 3 + if toneControls.Bass.Value != newBassLevel { + fmt.Printf(" Setting advanced bass to %d...\n", newBassLevel) - time.Sleep(1 * time.Second) - - // Adjust both bass and treble together - combinedBass := 2 - combinedTreble := 1 - fmt.Printf(" Setting bass to %d and treble to %d together...\n", combinedBass, combinedTreble) - err = soundtouchClient.SetAudioProductToneControls(&combinedBass, &combinedTreble) + err = soundtouchClient.SetAdvancedBass(newBassLevel) if err != nil { - log.Printf("āŒ Failed to set tone controls: %v", err) + log.Printf("āŒ Failed to set advanced bass: %v", err) } else { - fmt.Printf("āœ… Both tone controls adjusted\n") + fmt.Printf("āœ… Advanced bass adjusted\n") } } - time.Sleep(2 * time.Second) + time.Sleep(1 * time.Second) - // Example 4: Speaker Level Controls + // Adjust treble only + newTrebleLevel := -1 + if toneControls.Treble.Value != newTrebleLevel { + fmt.Printf(" Setting advanced treble to %d...\n", newTrebleLevel) + + err = soundtouchClient.SetAdvancedTreble(newTrebleLevel) + if err != nil { + log.Printf("āŒ Failed to set advanced treble: %v", err) + } else { + fmt.Printf("āœ… Advanced treble adjusted\n") + } + } + + time.Sleep(1 * time.Second) + + // Adjust both bass and treble together + combinedBass := 2 + combinedTreble := 1 + fmt.Printf(" Setting bass to %d and treble to %d together...\n", combinedBass, combinedTreble) + + err = soundtouchClient.SetAudioProductToneControls(&combinedBass, &combinedTreble) + if err != nil { + log.Printf("āŒ Failed to set tone controls: %v", err) + } else { + fmt.Printf("āœ… Both tone controls adjusted\n") + } +} + +func demonstrateLevelControls(soundtouchClient *client.Client) { fmt.Println("\n4. Speaker Level Controls...") levelControls, err := soundtouchClient.GetAudioProductLevelControls() if err != nil { log.Printf("āš ļø Speaker level controls not available on this device: %v", err) fmt.Println(" This feature is only available on surround sound systems") - } else { - fmt.Printf("šŸ”Š Current Speaker Levels: %s\n", levelControls.String()) - // Adjust front-center speaker level - newFrontCenterLevel := 2 - if levelControls.FrontCenterSpeakerLevel.Value != newFrontCenterLevel { - fmt.Printf(" Setting front-center speaker level to %d...\n", newFrontCenterLevel) - err = soundtouchClient.SetFrontCenterSpeakerLevel(newFrontCenterLevel) - if err != nil { - log.Printf("āŒ Failed to set front-center level: %v", err) - } else { - fmt.Printf("āœ… Front-center speaker level adjusted\n") - } - } + return + } - time.Sleep(1 * time.Second) + fmt.Printf("šŸ”Š Current Speaker Levels: %s\n", levelControls.String()) - // Adjust rear-surround speakers level - newRearSurroundLevel := -1 - if levelControls.RearSurroundSpeakersLevel.Value != newRearSurroundLevel { - fmt.Printf(" Setting rear-surround speakers level to %d...\n", newRearSurroundLevel) - err = soundtouchClient.SetRearSurroundSpeakersLevel(newRearSurroundLevel) - if err != nil { - log.Printf("āŒ Failed to set rear-surround level: %v", err) - } else { - fmt.Printf("āœ… Rear-surround speakers level adjusted\n") - } - } + // Adjust front-center speaker level + newFrontCenterLevel := 2 + if levelControls.FrontCenterSpeakerLevel.Value != newFrontCenterLevel { + fmt.Printf(" Setting front-center speaker level to %d...\n", newFrontCenterLevel) - time.Sleep(1 * time.Second) - - // Adjust both speaker levels together - combinedFrontCenter := 1 - combinedRearSurround := 0 - fmt.Printf(" Setting front-center to %d and rear-surround to %d together...\n", - combinedFrontCenter, combinedRearSurround) - err = soundtouchClient.SetAudioProductLevelControls(&combinedFrontCenter, &combinedRearSurround) + err = soundtouchClient.SetFrontCenterSpeakerLevel(newFrontCenterLevel) if err != nil { - log.Printf("āŒ Failed to set speaker levels: %v", err) + log.Printf("āŒ Failed to set front-center level: %v", err) } else { - fmt.Printf("āœ… Both speaker levels adjusted\n") + fmt.Printf("āœ… Front-center speaker level adjusted\n") } } - // Example 5: Compare with basic controls + time.Sleep(1 * time.Second) + + // Adjust rear-surround speakers level + newRearSurroundLevel := -1 + if levelControls.RearSurroundSpeakersLevel.Value != newRearSurroundLevel { + fmt.Printf(" Setting rear-surround speakers level to %d...\n", newRearSurroundLevel) + + err = soundtouchClient.SetRearSurroundSpeakersLevel(newRearSurroundLevel) + if err != nil { + log.Printf("āŒ Failed to set rear-surround level: %v", err) + } else { + fmt.Printf("āœ… Rear-surround speakers level adjusted\n") + } + } + + time.Sleep(1 * time.Second) + + // Adjust both speaker levels together + combinedFrontCenter := 1 + combinedRearSurround := 0 + fmt.Printf(" Setting front-center to %d and rear-surround to %d together...\n", + combinedFrontCenter, combinedRearSurround) + + err = soundtouchClient.SetAudioProductLevelControls(&combinedFrontCenter, &combinedRearSurround) + if err != nil { + log.Printf("āŒ Failed to set speaker levels: %v", err) + } else { + fmt.Printf("āœ… Both speaker levels adjusted\n") + } +} + +func demonstrateBasicControls(soundtouchClient *client.Client) { fmt.Println("\n5. Comparison with Basic Audio Controls...") fmt.Println(" Basic controls available on all devices:") @@ -215,24 +259,28 @@ func main() { } else { fmt.Printf(" Balance: %d (range: -50 to +50)\n", balance.TargetBalance) } +} - // Example 6: Error handling and validation +func demonstrateErrorHandling(soundtouchClient *client.Client) { fmt.Println("\n6. Error Handling Examples...") // Try to set invalid DSP controls to demonstrate validation fmt.Println(" Testing invalid audio mode...") - err = soundtouchClient.SetAudioMode("INVALID_MODE") + + err := soundtouchClient.SetAudioMode("INVALID_MODE") if err != nil { fmt.Printf("āš ļø Expected error for invalid mode: %v\n", err) } fmt.Println(" Testing negative video sync delay...") + err = soundtouchClient.SetVideoSyncAudioDelay(-10) if err != nil { fmt.Printf("āš ļø Expected error for negative delay: %v\n", err) } +} - // Example 7: CLI command equivalents +func showCLIEquivalents(deviceIP string) { fmt.Println("\n7. CLI Command Equivalents...") fmt.Println(" You can also use the CLI for these operations:") fmt.Println(" ") @@ -250,8 +298,9 @@ func main() { fmt.Printf(" soundtouch-cli audio level get --host %s\n", deviceIP) fmt.Printf(" soundtouch-cli audio level set --host %s --front-center 2 --rear-surround -1\n", deviceIP) fmt.Printf(" soundtouch-cli audio level front-center --host %s --level 3\n", deviceIP) +} - fmt.Println("\nšŸŽ‰ Advanced audio controls example completed!") +func printNotes() { fmt.Println("\nNotes:") fmt.Println("• Advanced audio controls are only available on professional/high-end devices") fmt.Println("• Consumer SoundTouch devices typically only support basic controls") diff --git a/examples/zone-slave-operations.go b/examples/zone-slave-operations/main.go similarity index 98% rename from examples/zone-slave-operations.go rename to examples/zone-slave-operations/main.go index 8f39c8a..792c104 100644 --- a/examples/zone-slave-operations.go +++ b/examples/zone-slave-operations/main.go @@ -1,3 +1,4 @@ +// Package main provides an example of using zone slave operations. package main import ( diff --git a/pkg/client/audio_test.go b/pkg/client/audio_test.go index cf2b923..ffba25b 100644 --- a/pkg/client/audio_test.go +++ b/pkg/client/audio_test.go @@ -44,6 +44,13 @@ func TestClient_GetAudioDSPControls(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/capabilities" { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(``)) + + return + } + if r.Method != "GET" { t.Errorf("Expected GET request, got %s", r.Method) } @@ -53,7 +60,7 @@ func TestClient_GetAudioDSPControls(t *testing.T) { } w.WriteHeader(tt.responseStatus) - w.Write([]byte(tt.responseBody)) + _, _ = w.Write([]byte(tt.responseBody)) })) defer server.Close() @@ -64,6 +71,7 @@ func TestClient_GetAudioDSPControls(t *testing.T) { if err == nil { t.Errorf("Expected error but got none") } + return } @@ -125,13 +133,23 @@ func TestClient_SetAudioDSPControls(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { callCount := 0 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { callCount++ + // Handle capabilities check + if r.URL.Path == "/capabilities" { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(``)) + + return + } + // First call might be GET for validation if r.Method == "GET" && r.URL.Path == "/audiodspcontrols" { w.WriteHeader(http.StatusOK) - w.Write([]byte(``)) + _, _ = w.Write([]byte(``)) + return } @@ -145,7 +163,7 @@ func TestClient_SetAudioDSPControls(t *testing.T) { } w.WriteHeader(tt.responseStatus) - w.Write([]byte(tt.responseBody)) + _, _ = w.Write([]byte(tt.responseBody)) })) defer server.Close() @@ -167,17 +185,27 @@ func TestClient_SetAudioDSPControls(t *testing.T) { func TestClient_SetAudioMode(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Handle capabilities check + if r.URL.Path == "/capabilities" { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(``)) + + return + } + // First call might be GET for validation if r.Method == "GET" && r.URL.Path == "/audiodspcontrols" { w.WriteHeader(http.StatusOK) - w.Write([]byte(``)) + _, _ = w.Write([]byte(``)) + return } // POST call for setting if r.Method == "POST" && r.URL.Path == "/audiodspcontrols" { w.WriteHeader(http.StatusOK) - w.Write([]byte(`OK`)) + _, _ = w.Write([]byte(`OK`)) + return } @@ -186,8 +214,8 @@ func TestClient_SetAudioMode(t *testing.T) { defer server.Close() client := createTestClient(server.URL) - err := client.SetAudioMode("MUSIC") + err := client.SetAudioMode("MUSIC") if err != nil { t.Errorf("Expected no error but got: %v", err) } @@ -228,18 +256,19 @@ func TestClient_SetVideoSyncAudioDelay(t *testing.T) { if err == nil { t.Errorf("Expected error but got none") } + return } - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) - w.Write([]byte(`OK`)) + _, _ = w.Write([]byte(`OK`)) })) defer server.Close() client := createTestClient(server.URL) - err := client.SetVideoSyncAudioDelay(tt.delay) + err := client.SetVideoSyncAudioDelay(tt.delay) if err != nil { t.Errorf("Expected no error but got: %v", err) } @@ -289,6 +318,13 @@ func TestClient_GetAudioProductToneControls(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/capabilities" { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(``)) + + return + } + if r.Method != "GET" { t.Errorf("Expected GET request, got %s", r.Method) } @@ -298,7 +334,7 @@ func TestClient_GetAudioProductToneControls(t *testing.T) { } w.WriteHeader(tt.responseStatus) - w.Write([]byte(tt.responseBody)) + _, _ = w.Write([]byte(tt.responseBody)) })) defer server.Close() @@ -309,6 +345,7 @@ func TestClient_GetAudioProductToneControls(t *testing.T) { if err == nil { t.Errorf("Expected error but got none") } + return } @@ -374,13 +411,22 @@ func TestClient_SetAudioProductToneControls(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Handle capabilities check + if r.URL.Path == "/capabilities" { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(``)) + + return + } + // First call might be GET for validation if r.Method == "GET" && r.URL.Path == "/audioproducttonecontrols" { w.WriteHeader(http.StatusOK) - w.Write([]byte(` + _, _ = w.Write([]byte(` `)) + return } @@ -394,7 +440,7 @@ func TestClient_SetAudioProductToneControls(t *testing.T) { } w.WriteHeader(tt.responseStatus) - w.Write([]byte(tt.responseBody)) + _, _ = w.Write([]byte(tt.responseBody)) })) defer server.Close() @@ -416,20 +462,30 @@ func TestClient_SetAudioProductToneControls(t *testing.T) { func TestClient_SetAdvancedBass(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Handle capabilities check + if r.URL.Path == "/capabilities" { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(``)) + + return + } + // First call might be GET for validation if r.Method == "GET" && r.URL.Path == "/audioproducttonecontrols" { w.WriteHeader(http.StatusOK) - w.Write([]byte(` + _, _ = w.Write([]byte(` `)) + return } // POST call for setting if r.Method == "POST" && r.URL.Path == "/audioproducttonecontrols" { w.WriteHeader(http.StatusOK) - w.Write([]byte(`OK`)) + _, _ = w.Write([]byte(`OK`)) + return } @@ -438,8 +494,8 @@ func TestClient_SetAdvancedBass(t *testing.T) { defer server.Close() client := createTestClient(server.URL) - err := client.SetAdvancedBass(5) + err := client.SetAdvancedBass(5) if err != nil { t.Errorf("Expected no error but got: %v", err) } @@ -447,20 +503,30 @@ func TestClient_SetAdvancedBass(t *testing.T) { func TestClient_SetAdvancedTreble(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Handle capabilities check + if r.URL.Path == "/capabilities" { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(``)) + + return + } + // First call might be GET for validation if r.Method == "GET" && r.URL.Path == "/audioproducttonecontrols" { w.WriteHeader(http.StatusOK) - w.Write([]byte(` + _, _ = w.Write([]byte(` `)) + return } // POST call for setting if r.Method == "POST" && r.URL.Path == "/audioproducttonecontrols" { w.WriteHeader(http.StatusOK) - w.Write([]byte(`OK`)) + _, _ = w.Write([]byte(`OK`)) + return } @@ -469,8 +535,8 @@ func TestClient_SetAdvancedTreble(t *testing.T) { defer server.Close() client := createTestClient(server.URL) - err := client.SetAdvancedTreble(-2) + err := client.SetAdvancedTreble(-2) if err != nil { t.Errorf("Expected no error but got: %v", err) } @@ -518,6 +584,13 @@ func TestClient_GetAudioProductLevelControls(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/capabilities" { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(``)) + + return + } + if r.Method != "GET" { t.Errorf("Expected GET request, got %s", r.Method) } @@ -527,7 +600,7 @@ func TestClient_GetAudioProductLevelControls(t *testing.T) { } w.WriteHeader(tt.responseStatus) - w.Write([]byte(tt.responseBody)) + _, _ = w.Write([]byte(tt.responseBody)) })) defer server.Close() @@ -538,6 +611,7 @@ func TestClient_GetAudioProductLevelControls(t *testing.T) { if err == nil { t.Errorf("Expected error but got none") } + return } @@ -605,13 +679,22 @@ func TestClient_SetAudioProductLevelControls(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Handle capabilities check + if r.URL.Path == "/capabilities" { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(``)) + + return + } + // First call might be GET for validation if r.Method == "GET" && r.URL.Path == "/audioproductlevelcontrols" { w.WriteHeader(http.StatusOK) - w.Write([]byte(` + _, _ = w.Write([]byte(` `)) + return } @@ -625,7 +708,7 @@ func TestClient_SetAudioProductLevelControls(t *testing.T) { } w.WriteHeader(tt.responseStatus) - w.Write([]byte(tt.responseBody)) + _, _ = w.Write([]byte(tt.responseBody)) })) defer server.Close() @@ -647,20 +730,30 @@ func TestClient_SetAudioProductLevelControls(t *testing.T) { func TestClient_SetFrontCenterSpeakerLevel(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Handle capabilities check + if r.URL.Path == "/capabilities" { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(``)) + + return + } + // First call might be GET for validation if r.Method == "GET" && r.URL.Path == "/audioproductlevelcontrols" { w.WriteHeader(http.StatusOK) - w.Write([]byte(` + _, _ = w.Write([]byte(` `)) + return } // POST call for setting if r.Method == "POST" && r.URL.Path == "/audioproductlevelcontrols" { w.WriteHeader(http.StatusOK) - w.Write([]byte(`OK`)) + _, _ = w.Write([]byte(`OK`)) + return } @@ -669,8 +762,8 @@ func TestClient_SetFrontCenterSpeakerLevel(t *testing.T) { defer server.Close() client := createTestClient(server.URL) - err := client.SetFrontCenterSpeakerLevel(5) + err := client.SetFrontCenterSpeakerLevel(5) if err != nil { t.Errorf("Expected no error but got: %v", err) } @@ -678,20 +771,30 @@ func TestClient_SetFrontCenterSpeakerLevel(t *testing.T) { func TestClient_SetRearSurroundSpeakersLevel(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Handle capabilities check + if r.URL.Path == "/capabilities" { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(``)) + + return + } + // First call might be GET for validation if r.Method == "GET" && r.URL.Path == "/audioproductlevelcontrols" { w.WriteHeader(http.StatusOK) - w.Write([]byte(` + _, _ = w.Write([]byte(` `)) + return } // POST call for setting if r.Method == "POST" && r.URL.Path == "/audioproductlevelcontrols" { w.WriteHeader(http.StatusOK) - w.Write([]byte(`OK`)) + _, _ = w.Write([]byte(`OK`)) + return } @@ -700,8 +803,8 @@ func TestClient_SetRearSurroundSpeakersLevel(t *testing.T) { defer server.Close() client := createTestClient(server.URL) - err := client.SetRearSurroundSpeakersLevel(-3) + err := client.SetRearSurroundSpeakersLevel(-3) if err != nil { t.Errorf("Expected no error but got: %v", err) } @@ -742,6 +845,7 @@ func TestClient_AudioEndpoints_NetworkError(t *testing.T) { bass := 5 treble := -2 + err = client.SetAudioProductToneControls(&bass, &treble) if err == nil { t.Errorf("Expected network error for SetAudioProductToneControls but got none") @@ -764,6 +868,7 @@ func TestClient_AudioEndpoints_NetworkError(t *testing.T) { frontCenter := 2 rearSurround := -1 + err = client.SetAudioProductLevelControls(&frontCenter, &rearSurround) if err == nil { t.Errorf("Expected network error for SetAudioProductLevelControls but got none") diff --git a/pkg/client/client.go b/pkg/client/client.go index fad9f41..1de7471 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -1075,6 +1075,7 @@ func (c *Client) GetAudioDSPControls() (*models.AudioDSPControls, error) { } var dspControls models.AudioDSPControls + err = c.get("/audiodspcontrols", &dspControls) return &dspControls, err @@ -1146,6 +1147,7 @@ func (c *Client) GetAudioProductToneControls() (*models.AudioProductToneControls } var toneControls models.AudioProductToneControls + err = c.get("/audioproducttonecontrols", &toneControls) return &toneControls, err @@ -1199,6 +1201,7 @@ func (c *Client) GetAudioProductLevelControls() (*models.AudioProductLevelContro } var levelControls models.AudioProductLevelControls + err = c.get("/audioproductlevelcontrols", &levelControls) return &levelControls, err diff --git a/pkg/client/system_test.go b/pkg/client/system_test.go index f9407c0..e9456f8 100644 --- a/pkg/client/system_test.go +++ b/pkg/client/system_test.go @@ -296,6 +296,18 @@ func TestClient_SetClockDisplay(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + if tt.expectError && tt.statusCode == 0 { + // For client-side validation errors, we don't need a server + client := createTestClient("http://localhost:8080") + + err := client.SetClockDisplay(tt.request) + if err == nil { + t.Error("Expected error, got none") + } + + return + } + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/clockDisplay" { t.Errorf("Expected path '/clockDisplay', got '%s'", r.URL.Path) @@ -305,7 +317,11 @@ func TestClient_SetClockDisplay(t *testing.T) { t.Errorf("Expected POST method, got '%s'", r.Method) } - w.WriteHeader(tt.statusCode) + if tt.statusCode != 0 { + w.WriteHeader(tt.statusCode) + } else { + w.WriteHeader(http.StatusOK) + } })) defer server.Close() diff --git a/pkg/client/zone_slave_test.go b/pkg/client/zone_slave_test.go index 00138ae..912a8d0 100644 --- a/pkg/client/zone_slave_test.go +++ b/pkg/client/zone_slave_test.go @@ -94,9 +94,11 @@ func TestClient_AddZoneSlave(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var receivedMethod string - var receivedPath string - var receivedBody string + var ( + receivedMethod string + receivedPath string + receivedBody string + ) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { receivedMethod = r.Method @@ -104,12 +106,12 @@ func TestClient_AddZoneSlave(t *testing.T) { if r.Method == "POST" { body := make([]byte, r.ContentLength) - r.Body.Read(body) + _, _ = r.Body.Read(body) receivedBody = string(body) } w.WriteHeader(tt.responseStatus) - w.Write([]byte(tt.responseBody)) + _, _ = w.Write([]byte(tt.responseBody)) })) defer server.Close() @@ -122,6 +124,7 @@ func TestClient_AddZoneSlave(t *testing.T) { if err == nil { t.Errorf("Expected error but got none") } + return } @@ -171,7 +174,7 @@ func TestClient_AddZoneSlaveByDeviceID(t *testing.T) { // Read and verify body body := make([]byte, r.ContentLength) - r.Body.Read(body) + _, _ = r.Body.Read(body) bodyStr := string(body) if !strings.Contains(bodyStr, `MASTER123`) { @@ -188,7 +191,7 @@ func TestClient_AddZoneSlaveByDeviceID(t *testing.T) { } w.WriteHeader(http.StatusOK) - w.Write([]byte(`OK`)) + _, _ = w.Write([]byte(`OK`)) })) defer server.Close() @@ -255,9 +258,11 @@ func TestClient_RemoveZoneSlave(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var receivedMethod string - var receivedPath string - var receivedBody string + var ( + receivedMethod string + receivedPath string + receivedBody string + ) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { receivedMethod = r.Method @@ -265,12 +270,12 @@ func TestClient_RemoveZoneSlave(t *testing.T) { if r.Method == "POST" { body := make([]byte, r.ContentLength) - r.Body.Read(body) + _, _ = r.Body.Read(body) receivedBody = string(body) } w.WriteHeader(tt.responseStatus) - w.Write([]byte(tt.responseBody)) + _, _ = w.Write([]byte(tt.responseBody)) })) defer server.Close() @@ -283,6 +288,7 @@ func TestClient_RemoveZoneSlave(t *testing.T) { if err == nil { t.Errorf("Expected error but got none") } + return } @@ -332,7 +338,7 @@ func TestClient_RemoveZoneSlaveByDeviceID(t *testing.T) { // Read and verify body body := make([]byte, r.ContentLength) - r.Body.Read(body) + _, _ = r.Body.Read(body) bodyStr := string(body) if !strings.Contains(bodyStr, `MASTER123`) { @@ -344,7 +350,7 @@ func TestClient_RemoveZoneSlaveByDeviceID(t *testing.T) { } w.WriteHeader(http.StatusOK) - w.Write([]byte(`OK`)) + _, _ = w.Write([]byte(`OK`)) })) defer server.Close() @@ -507,6 +513,7 @@ func TestZoneSlaveRequest_HelperMethods(t *testing.T) { request.AddSlave("SLAVE456", "192.168.1.101") str := request.String() + expected := "Zone slave operation: master=MASTER123, slave=SLAVE456 (192.168.1.101)" if str != expected { t.Errorf("Expected string '%s', got '%s'", expected, str) @@ -518,6 +525,7 @@ func TestZoneSlaveRequest_HelperMethods(t *testing.T) { request.AddSlave("SLAVE456", "") str := request.String() + expected := "Zone slave operation: master=MASTER123, slave=SLAVE456" if str != expected { t.Errorf("Expected string '%s', got '%s'", expected, str) diff --git a/pkg/models/audio.go b/pkg/models/audio.go index 3c5fe31..fa63185 100644 --- a/pkg/models/audio.go +++ b/pkg/models/audio.go @@ -129,6 +129,7 @@ func (adsp *AudioDSPControls) GetSupportedAudioModes() []string { if adsp.SupportedAudioModes == "" { return []string{} } + return strings.Split(adsp.SupportedAudioModes, "|") } @@ -140,12 +141,14 @@ func (adsp *AudioDSPControls) IsAudioModeSupported(mode string) bool { return true } } + return false } // String returns a human-readable string representation of DSP controls func (adsp *AudioDSPControls) String() string { supportedModes := strings.Join(adsp.GetSupportedAudioModes(), ", ") + return fmt.Sprintf("Audio Mode: %s, Video Sync Delay: %d ms, Supported Modes: [%s]", adsp.AudioMode, adsp.VideoSyncAudioDelay, supportedModes) } @@ -171,6 +174,7 @@ func (bc *BassControlSetting) ValidateBass(value int) error { if value < bc.MinValue || value > bc.MaxValue { return fmt.Errorf("bass value %d is outside valid range [%d, %d]", value, bc.MinValue, bc.MaxValue) } + return nil } @@ -179,9 +183,11 @@ func (bc *BassControlSetting) ClampValue(value int) int { if value < bc.MinValue { return bc.MinValue } + if value > bc.MaxValue { return bc.MaxValue } + return value } @@ -190,6 +196,7 @@ func (tc *TrebleControlSetting) ValidateTreble(value int) error { if value < tc.MinValue || value > tc.MaxValue { return fmt.Errorf("treble value %d is outside valid range [%d, %d]", value, tc.MinValue, tc.MaxValue) } + return nil } @@ -198,9 +205,11 @@ func (tc *TrebleControlSetting) ClampValue(value int) int { if value < tc.MinValue { return tc.MinValue } + if value > tc.MaxValue { return tc.MaxValue } + return value } @@ -249,6 +258,7 @@ func (fc *FrontCenterLevelSetting) ValidateLevel(value int) error { if value < fc.MinValue || value > fc.MaxValue { return fmt.Errorf("front-center speaker level %d is outside valid range [%d, %d]", value, fc.MinValue, fc.MaxValue) } + return nil } @@ -257,9 +267,11 @@ func (fc *FrontCenterLevelSetting) ClampLevel(value int) int { if value < fc.MinValue { return fc.MinValue } + if value > fc.MaxValue { return fc.MaxValue } + return value } @@ -268,6 +280,7 @@ func (rs *RearSurroundLevelSetting) ValidateLevel(value int) error { if value < rs.MinValue || value > rs.MaxValue { return fmt.Errorf("rear-surround speaker level %d is outside valid range [%d, %d]", value, rs.MinValue, rs.MaxValue) } + return nil } @@ -276,9 +289,11 @@ func (rs *RearSurroundLevelSetting) ClampLevel(value int) int { if value < rs.MinValue { return rs.MinValue } + if value > rs.MaxValue { return rs.MaxValue } + return value } @@ -337,15 +352,19 @@ func (ac *AudioCapabilities) HasAdvancedAudioControls() bool { // GetAvailableControls returns a list of available advanced audio controls func (ac *AudioCapabilities) GetAvailableControls() []string { var controls []string + if ac.DSPControls { controls = append(controls, "DSP Controls") } + if ac.ProductToneControls { controls = append(controls, "Tone Controls") } + if ac.ProductLevelControls { controls = append(controls, "Level Controls") } + return controls } @@ -356,5 +375,6 @@ func (ac *AudioCapabilities) String() string { } controls := ac.GetAvailableControls() + return fmt.Sprintf("Available controls: %s", strings.Join(controls, ", ")) } diff --git a/pkg/models/audio_test.go b/pkg/models/audio_test.go index e26758e..e9e6992 100644 --- a/pkg/models/audio_test.go +++ b/pkg/models/audio_test.go @@ -160,6 +160,7 @@ func TestAudioDSPControlsRequest_Validate(t *testing.T) { t.Errorf("Expected error but got none") return } + if !strings.Contains(err.Error(), tt.errorMsg) { t.Errorf("Expected error message to contain '%s', got '%s'", tt.errorMsg, err.Error()) } @@ -330,6 +331,7 @@ func TestAudioProductToneControlsRequest_Validate(t *testing.T) { t.Errorf("Expected error but got none") return } + if !strings.Contains(err.Error(), tt.errorMsg) { t.Errorf("Expected error message to contain '%s', got '%s'", tt.errorMsg, err.Error()) } @@ -455,6 +457,7 @@ func TestAudioProductLevelControlsRequest_Validate(t *testing.T) { t.Errorf("Expected error but got none") return } + if !strings.Contains(err.Error(), tt.errorMsg) { t.Errorf("Expected error message to contain '%s', got '%s'", tt.errorMsg, err.Error()) } diff --git a/pkg/models/zone.go b/pkg/models/zone.go index 2e075cc..8244da2 100644 --- a/pkg/models/zone.go +++ b/pkg/models/zone.go @@ -452,6 +452,7 @@ func (zsr *ZoneSlaveRequest) GetSlaveDeviceID() string { if len(zsr.Members) > 0 { return zsr.Members[0].DeviceID } + return "" } @@ -460,6 +461,7 @@ func (zsr *ZoneSlaveRequest) GetSlaveIP() string { if len(zsr.Members) > 0 { return zsr.Members[0].IP } + return "" } diff --git a/pkg/models/zone_slave_test.go b/pkg/models/zone_slave_test.go index d4ee583..2b13b2d 100644 --- a/pkg/models/zone_slave_test.go +++ b/pkg/models/zone_slave_test.go @@ -151,6 +151,7 @@ func TestZoneSlaveRequest_HelperMethods(t *testing.T) { request.AddSlave("SLAVE456", "192.168.1.101") deviceID := request.GetSlaveDeviceID() + expected := "SLAVE456" if deviceID != expected { t.Errorf("Expected device ID '%s', got '%s'", expected, deviceID) @@ -171,6 +172,7 @@ func TestZoneSlaveRequest_HelperMethods(t *testing.T) { request.AddSlave("SLAVE456", "192.168.1.101") ip := request.GetSlaveIP() + expected := "192.168.1.101" if ip != expected { t.Errorf("Expected IP '%s', got '%s'", expected, ip) @@ -208,6 +210,7 @@ func TestZoneSlaveRequest_String(t *testing.T) { setup: func() *ZoneSlaveRequest { req := NewZoneSlaveRequest("MASTER123") req.AddSlave("SLAVE456", "192.168.1.101") + return req }, expected: "Zone slave operation: master=MASTER123, slave=SLAVE456 (192.168.1.101)", @@ -217,6 +220,7 @@ func TestZoneSlaveRequest_String(t *testing.T) { setup: func() *ZoneSlaveRequest { req := NewZoneSlaveRequest("MASTER123") req.AddSlave("SLAVE456", "") + return req }, expected: "Zone slave operation: master=MASTER123, slave=SLAVE456", @@ -330,12 +334,14 @@ func TestZoneSlaveRequest_XMLUnmarshaling(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var request ZoneSlaveRequest + err := xml.Unmarshal([]byte(tt.xmlData), &request) if tt.expectError { if err == nil { t.Errorf("Expected error but got none") } + return } @@ -381,6 +387,7 @@ func TestZoneSlaveEntry_XMLMarshaling(t *testing.T) { } xmlStr := string(xmlData) + expected := `SLAVE456` if xmlStr != expected { t.Errorf("Expected XML '%s', got '%s'", expected, xmlStr) @@ -399,6 +406,7 @@ func TestZoneSlaveEntry_XMLMarshaling(t *testing.T) { } xmlStr := string(xmlData) + expected := `SLAVE456` if xmlStr != expected { t.Errorf("Expected XML '%s', got '%s'", expected, xmlStr)