package client import ( "os" "strings" "testing" "time" "github.com/gesellix/bose-soundtouch/pkg/models" ) func TestClient_Navigation_Integration(t *testing.T) { if testing.Short() { t.Skip("Skipping integration tests in short mode") } host := os.Getenv("SOUNDTOUCH_TEST_HOST") if host == "" { t.Skip("SOUNDTOUCH_TEST_HOST environment variable not set - skipping integration tests") } // Parse host:port if provided var finalHost string var finalPort int if strings.Contains(host, ":") { parts := strings.Split(host, ":") finalHost = parts[0] if len(parts) > 1 { // Use default port if parsing fails finalPort = 8090 } } else { finalHost = host finalPort = 8090 } config := &Config{ Host: finalHost, Port: finalPort, Timeout: 30 * time.Second, UserAgent: "Bose-SoundTouch-Go-Client-Integration-Test/1.0", } client := NewClient(config) t.Run("Navigate_TuneIn", func(t *testing.T) { response, err := client.Navigate("TUNEIN", "", 1, 10) if err != nil { t.Logf("Navigate TUNEIN failed (may not be available): %v", err) t.Skip("TUNEIN not available on test device") return } t.Logf("✓ Navigate TUNEIN succeeded") t.Logf(" Total items: %d", response.TotalItems) t.Logf(" Items returned: %d", len(response.Items)) if response.TotalItems > 0 { t.Logf(" First item: %s", response.Items[0].GetDisplayName()) } }) t.Run("GetTuneInStations", func(t *testing.T) { response, err := client.GetTuneInStations("") if err != nil { t.Logf("GetTuneInStations failed (may not be available): %v", err) t.Skip("TuneIn not available on test device") return } t.Logf("✓ GetTuneInStations succeeded") t.Logf(" Total stations: %d", response.TotalItems) stations := response.GetStations() t.Logf(" Station items: %d", len(stations)) }) t.Run("Navigate_StoredMusic", func(t *testing.T) { // Get sources first to check if STORED_MUSIC is available sources, err := client.GetSources() if err != nil { t.Fatalf("Failed to get sources: %v", err) } var storedMusicAccount string for _, source := range sources.SourceItem { if source.Source == "STORED_MUSIC" && source.Status.IsReady() { storedMusicAccount = source.SourceAccount break } } if storedMusicAccount == "" { t.Skip("STORED_MUSIC not available or not ready on test device") } response, err := client.GetStoredMusicLibrary(storedMusicAccount) if err != nil { t.Logf("GetStoredMusicLibrary failed: %v", err) return } t.Logf("✓ GetStoredMusicLibrary succeeded") t.Logf(" Source account: %s", storedMusicAccount) t.Logf(" Total items: %d", response.TotalItems) directories := response.GetDirectories() t.Logf(" Directories: %d", len(directories)) tracks := response.GetTracks() t.Logf(" Tracks: %d", len(tracks)) }) t.Run("SearchStation_TuneIn", func(t *testing.T) { response, err := client.SearchTuneInStations("jazz") if err != nil { t.Logf("SearchTuneInStations failed (may not be supported): %v", err) t.Skip("TuneIn search not supported on test device") return } t.Logf("✓ SearchTuneInStations succeeded") t.Logf(" Search term: jazz") t.Logf(" Total results: %d", response.GetResultCount()) songs := response.GetSongs() artists := response.GetArtists() stations := response.GetStations() t.Logf(" Songs: %d", len(songs)) t.Logf(" Artists: %d", len(artists)) t.Logf(" Stations: %d", len(stations)) if len(stations) > 0 { station := stations[0] t.Logf(" First station: %s", station.GetDisplayName()) if station.Token != "" { t.Logf(" Station token: %s", station.Token) } } }) } func TestClient_StationManagement_Integration(t *testing.T) { if testing.Short() { t.Skip("Skipping integration tests in short mode") } host := os.Getenv("SOUNDTOUCH_TEST_HOST") if host == "" { t.Skip("SOUNDTOUCH_TEST_HOST environment variable not set - skipping integration tests") } // Parse host:port if provided var finalHost string var finalPort int if strings.Contains(host, ":") { parts := strings.Split(host, ":") finalHost = parts[0] if len(parts) > 1 { finalPort = 8090 } } else { finalHost = host finalPort = 8090 } config := &Config{ Host: finalHost, Port: finalPort, Timeout: 30 * time.Second, UserAgent: "Bose-SoundTouch-Go-Client-Integration-Test/1.0", } client := NewClient(config) t.Run("SearchAndAddStation_Pandora", func(t *testing.T) { // Get sources first to check if Pandora is available sources, err := client.GetSources() if err != nil { t.Fatalf("Failed to get sources: %v", err) } var pandoraAccount string for _, source := range sources.SourceItem { if source.Source == "PANDORA" && source.Status.IsReady() { pandoraAccount = source.SourceAccount break } } if pandoraAccount == "" { t.Skip("Pandora not available or not configured on test device") } // Search for stations searchResponse, err := client.SearchPandoraStations(pandoraAccount, "classic rock") if err != nil { t.Logf("SearchPandoraStations failed: %v", err) t.Skip("Pandora search not working") return } t.Logf("✓ SearchPandoraStations succeeded") t.Logf(" Account: %s", pandoraAccount) t.Logf(" Results: %d", searchResponse.GetResultCount()) // Try to find an artist or station result to add var tokenToAdd string var nameToAdd string artists := searchResponse.GetArtists() if len(artists) > 0 { tokenToAdd = artists[0].Token nameToAdd = artists[0].Name + " Radio" } else { stations := searchResponse.GetStations() if len(stations) > 0 { tokenToAdd = stations[0].Token nameToAdd = stations[0].Name } } if tokenToAdd == "" { t.Skip("No suitable results found to test AddStation") } t.Logf(" Will attempt to add: %s (Token: %s)", nameToAdd, tokenToAdd) // Note: AddStation immediately starts playing and modifies user's collection // In a real integration test, you might want to skip this or use a test account t.Logf(" Skipping actual AddStation to avoid modifying user collection") t.Logf(" AddStation would call: client.AddStation(%q, %q, %q, %q)", "PANDORA", pandoraAccount, tokenToAdd, nameToAdd) }) t.Run("NavigateContainer_Integration", func(t *testing.T) { // Get sources to find a suitable container-based source sources, err := client.GetSources() if err != nil { t.Fatalf("Failed to get sources: %v", err) } var testSource string var testAccount string // Look for STORED_MUSIC as it typically has containers for _, source := range sources.SourceItem { if source.Source == "STORED_MUSIC" && source.Status.IsReady() { testSource = source.Source testAccount = source.SourceAccount break } } if testSource == "" { t.Skip("No suitable container-based source found") } // First, navigate to get a container response, err := client.Navigate(testSource, testAccount, 1, 10) if err != nil { t.Logf("Initial navigate failed: %v", err) return } directories := response.GetDirectories() if len(directories) == 0 { t.Skip("No directories found to test container navigation") } // Pick the first directory to navigate into container := directories[0] if container.ContentItem == nil { t.Skip("Directory has no ContentItem for navigation") } t.Logf("✓ Found container: %s", container.GetDisplayName()) // Navigate into the container containerResponse, err := client.NavigateContainer(testSource, testAccount, 1, 20, container.ContentItem) if err != nil { t.Logf("NavigateContainer failed: %v", err) return } t.Logf("✓ NavigateContainer succeeded") t.Logf(" Container: %s", container.GetDisplayName()) t.Logf(" Items in container: %d", len(containerResponse.Items)) tracks := containerResponse.GetTracks() subdirs := containerResponse.GetDirectories() t.Logf(" Tracks: %d", len(tracks)) t.Logf(" Subdirectories: %d", len(subdirs)) }) } func TestClient_Navigation_ErrorHandling_Integration(t *testing.T) { if testing.Short() { t.Skip("Skipping integration tests in short mode") } host := os.Getenv("SOUNDTOUCH_TEST_HOST") if host == "" { t.Skip("SOUNDTOUCH_TEST_HOST environment variable not set - skipping integration tests") } // Parse host:port if provided var finalHost string var finalPort int if strings.Contains(host, ":") { parts := strings.Split(host, ":") finalHost = parts[0] if len(parts) > 1 { finalPort = 8090 } } else { finalHost = host finalPort = 8090 } config := &Config{ Host: finalHost, Port: finalPort, Timeout: 10 * time.Second, UserAgent: "Bose-SoundTouch-Go-Client-Integration-Test/1.0", } client := NewClient(config) t.Run("Navigate_InvalidSource", func(t *testing.T) { _, err := client.Navigate("INVALID_SOURCE", "", 1, 10) if err == nil { t.Error("Expected error for invalid source, got none") } else { t.Logf("✓ Correctly failed for invalid source: %v", err) } }) t.Run("SearchStation_InvalidSource", func(t *testing.T) { _, err := client.SearchStation("INVALID_SOURCE", "", "test") if err == nil { t.Error("Expected error for invalid source, got none") } else { t.Logf("✓ Correctly failed for invalid source: %v", err) } }) t.Run("AddStation_InvalidToken", func(t *testing.T) { err := client.AddStation("PANDORA", "fake_account", "invalid_token", "Test Station") if err == nil { t.Error("Expected error for invalid token, got none") } else { t.Logf("✓ Correctly failed for invalid token: %v", err) } }) t.Run("RemoveStation_InvalidContentItem", func(t *testing.T) { invalidContentItem := &models.ContentItem{ Source: "PANDORA", Location: "invalid_location", ItemName: "Invalid Station", } err := client.RemoveStation(invalidContentItem) if err == nil { t.Error("Expected error for invalid content item, got none") } else { t.Logf("✓ Correctly failed for invalid content item: %v", err) } }) } func BenchmarkClient_Navigate_Integration(b *testing.B) { if testing.Short() { b.Skip("Skipping integration benchmarks in short mode") } host := os.Getenv("SOUNDTOUCH_TEST_HOST") if host == "" { b.Skip("SOUNDTOUCH_TEST_HOST environment variable not set - skipping integration benchmarks") } // Parse host:port if provided var finalHost string var finalPort int if strings.Contains(host, ":") { parts := strings.Split(host, ":") finalHost = parts[0] if len(parts) > 1 { finalPort = 8090 } } else { finalHost = host finalPort = 8090 } config := &Config{ Host: finalHost, Port: finalPort, Timeout: 10 * time.Second, UserAgent: "Bose-SoundTouch-Go-Client-Benchmark/1.0", } client := NewClient(config) b.ResetTimer() b.Run("Navigate_TuneIn", func(b *testing.B) { for i := 0; i < b.N; i++ { _, err := client.Navigate("TUNEIN", "", 1, 10) if err != nil { b.Logf("Navigate failed: %v", err) b.Skip("TuneIn not available") return } } }) b.Run("SearchStation_TuneIn", func(b *testing.B) { searchTerms := []string{"jazz", "rock", "classical", "pop", "country"} for i := 0; i < b.N; i++ { term := searchTerms[i%len(searchTerms)] _, err := client.SearchTuneInStations(term) if err != nil { b.Logf("Search failed: %v", err) b.Skip("TuneIn search not available") return } } }) }