From 369ebc42fed24cb251f27e396e699fe50f7b054b Mon Sep 17 00:00:00 2001 From: Tobias Gesellchen Date: Sun, 11 Jan 2026 00:39:58 +0100 Subject: [PATCH] Fix golangci-lint findings and improve code quality Move example files to separate packages to avoid main redeclaration. Fix cyclomatic complexity and variable shadowing. Address errcheck and wsl linting issues. Update tests to handle capabilities and fix panics. Apply consistent formatting with gofmt. --- cmd/soundtouch-cli/cmd_audio.go | 28 +- .../main.go} | 271 +++++++++++------- .../main.go} | 1 + pkg/client/audio_test.go | 159 ++++++++-- pkg/client/client.go | 3 + pkg/client/system_test.go | 18 +- pkg/client/zone_slave_test.go | 36 ++- pkg/models/audio.go | 20 ++ pkg/models/audio_test.go | 3 + pkg/models/zone.go | 2 + pkg/models/zone_slave_test.go | 8 + 11 files changed, 388 insertions(+), 161 deletions(-) rename examples/{advanced-audio-controls.go => advanced-audio-controls/main.go} (58%) rename examples/{zone-slave-operations.go => zone-slave-operations/main.go} (98%) 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)