mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
style: apply golangci-lint --fix for all remaining issues
Applied automatic fixes using golangci-lint --fix which resolved: - All remaining wsl_v5 whitespace issues (28 issues) - All whitespace formatting issues (1 issue) - Improved code formatting consistency across the entire codebase All tests passing and functionality preserved.
This commit is contained in:
@@ -298,6 +298,7 @@ func printFeatureCategories(featuresByCategory map[string][]models.EndpointFeatu
|
||||
for _, feature := range features {
|
||||
printFeatureStatus(feature, supportedURLs, verbose)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
@@ -315,6 +316,7 @@ func printFeatureStatus(feature models.EndpointFeature, supportedURLs *models.Su
|
||||
if feature.Essential {
|
||||
fmt.Printf(" ⭐")
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
if verbose {
|
||||
@@ -342,6 +344,7 @@ func printVerboseFeatureDetails(feature models.EndpointFeature, supportedEndpoin
|
||||
if supportedEndpoints < len(feature.Endpoints) {
|
||||
fmt.Printf(" (partial)")
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
|
||||
@@ -353,6 +356,7 @@ func printMissingEssentialFeatures(supportedURLs *models.SupportedURLsResponse)
|
||||
for _, feature := range missingEssential {
|
||||
fmt.Printf(" ❌ %s - %s\n", feature.Name, feature.Description)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
@@ -361,16 +365,20 @@ func printPartiallyImplementedFeatures(supportedURLs *models.SupportedURLsRespon
|
||||
partial := supportedURLs.GetPartiallyImplementedFeatures()
|
||||
if len(partial) > 0 && verbose {
|
||||
fmt.Printf("⚠️ Partially Supported Features:\n")
|
||||
|
||||
for _, feature := range partial {
|
||||
fmt.Printf(" 🟡 %s\n", feature.Name)
|
||||
|
||||
for _, endpoint := range feature.Endpoints {
|
||||
status := "❌"
|
||||
if supportedURLs.HasURL(endpoint) {
|
||||
status = "✅"
|
||||
}
|
||||
|
||||
fmt.Printf(" %s %s\n", status, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
@@ -387,6 +395,7 @@ func printDetailedEndpoints(supportedURLs *models.SupportedURLsResponse) {
|
||||
for _, url := range coreURLs {
|
||||
fmt.Printf(" • %s\n", url)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
@@ -398,6 +407,7 @@ func printDetailedEndpoints(supportedURLs *models.SupportedURLsResponse) {
|
||||
for _, url := range streamingURLs {
|
||||
fmt.Printf(" • %s\n", url)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
@@ -409,6 +419,7 @@ func printDetailedEndpoints(supportedURLs *models.SupportedURLsResponse) {
|
||||
for _, url := range advancedURLs {
|
||||
fmt.Printf(" • %s\n", url)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
@@ -420,11 +431,13 @@ func printDetailedEndpoints(supportedURLs *models.SupportedURLsResponse) {
|
||||
for _, url := range networkURLs {
|
||||
fmt.Printf(" • %s\n", url)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// Show all supported URLs
|
||||
fmt.Printf("📝 Complete Endpoint List:\n")
|
||||
|
||||
allURLs := supportedURLs.GetURLs()
|
||||
for i, url := range allURLs {
|
||||
fmt.Printf(" %3d. %s\n", i+1, url)
|
||||
@@ -449,6 +462,7 @@ func getDeviceAnalysis(c *cli.Context) error {
|
||||
}
|
||||
|
||||
printDeviceAnalysis(supportedURLs)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -469,10 +483,12 @@ func printDeviceAnalysis(supportedURLs *models.SupportedURLsResponse) {
|
||||
missingEssential := supportedURLs.GetMissingEssentialFeatures()
|
||||
if len(missingEssential) > 0 {
|
||||
fmt.Printf("❌ Missing Essential Features:\n")
|
||||
|
||||
for _, feature := range missingEssential {
|
||||
fmt.Printf(" • %s - %s\n", feature.Name, feature.Description)
|
||||
fmt.Printf(" Impact: Device may not function properly without this\n")
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
} else {
|
||||
fmt.Printf("✅ All essential features are supported\n\n")
|
||||
@@ -481,14 +497,17 @@ func printDeviceAnalysis(supportedURLs *models.SupportedURLsResponse) {
|
||||
// Show what works
|
||||
supportedFeatures := supportedURLs.GetSupportedFeatures()
|
||||
fmt.Printf("✅ Available Features (%d):\n", len(supportedFeatures))
|
||||
|
||||
categoryCount := make(map[string]int)
|
||||
for _, feature := range supportedFeatures {
|
||||
categoryCount[feature.Category]++
|
||||
}
|
||||
|
||||
for category, count := range categoryCount {
|
||||
emoji := getCategoryEmoji(category)
|
||||
fmt.Printf(" %s %s: %d features\n", emoji, category, count)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
|
||||
// Show what's missing
|
||||
@@ -499,6 +518,7 @@ func printDeviceAnalysis(supportedURLs *models.SupportedURLsResponse) {
|
||||
for _, feature := range unsupportedFeatures {
|
||||
fmt.Printf(" • %s - %s\n", feature.Name, feature.Description)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
@@ -515,8 +535,10 @@ func printDeviceAnalysis(supportedURLs *models.SupportedURLsResponse) {
|
||||
supportedCount++
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf(" • %s (%d/%d endpoints)\n", feature.Name, supportedCount, len(feature.Endpoints))
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ func browseContent(c *cli.Context) error {
|
||||
}
|
||||
|
||||
printNavigationResults(response, "Content")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -58,6 +59,7 @@ func browseWithMenu(c *cli.Context) error {
|
||||
}
|
||||
|
||||
printNavigationResults(response, "Menu Items")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -93,6 +95,7 @@ func browseContainer(c *cli.Context) error {
|
||||
}
|
||||
|
||||
printNavigationResults(response, "Container Contents")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -127,6 +130,7 @@ func browseTuneIn(c *cli.Context) error {
|
||||
}
|
||||
|
||||
printNavigationResults(response, "TuneIn Stations")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -155,6 +159,7 @@ func browsePandora(c *cli.Context) error {
|
||||
}
|
||||
|
||||
printNavigationResults(response, "Pandora Stations")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -183,6 +188,7 @@ func browseStoredMusic(c *cli.Context) error {
|
||||
}
|
||||
|
||||
printNavigationResults(response, "Stored Music Library")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -203,6 +209,7 @@ func printNavigationResults(response *models.NavigateResponse, title string) {
|
||||
}
|
||||
|
||||
fmt.Printf(" Items:\n")
|
||||
|
||||
for i, item := range response.Items {
|
||||
printNavigationItem(item, i+1, response.Source)
|
||||
}
|
||||
@@ -230,9 +237,11 @@ func printContentItemInfo(item models.NavigateItem, responseSource string) {
|
||||
if item.ContentItem.Source != "" && item.ContentItem.Source != responseSource {
|
||||
fmt.Printf(" Source: %s\n", item.ContentItem.Source)
|
||||
}
|
||||
|
||||
if item.Type != "" {
|
||||
fmt.Printf(" Type: %s\n", item.Type)
|
||||
}
|
||||
|
||||
if item.ContentItem.Location != "" && len(item.ContentItem.Location) < 100 {
|
||||
fmt.Printf(" Location: %s\n", item.ContentItem.Location)
|
||||
}
|
||||
@@ -243,6 +252,7 @@ func printItemMetadata(item models.NavigateItem) {
|
||||
if item.ArtistName != "" {
|
||||
fmt.Printf(" Artist: %s\n", item.ArtistName)
|
||||
}
|
||||
|
||||
if item.AlbumName != "" {
|
||||
fmt.Printf(" Album: %s\n", item.AlbumName)
|
||||
}
|
||||
|
||||
@@ -45,9 +45,11 @@ func getNowPlaying(c *cli.Context) error {
|
||||
// printBasicPlaybackInfo prints basic source and status information
|
||||
func printBasicPlaybackInfo(nowPlaying *models.NowPlaying) {
|
||||
fmt.Printf(" Source: %s\n", nowPlaying.Source)
|
||||
|
||||
if nowPlaying.SourceAccount != "" {
|
||||
fmt.Printf(" Source Account: %s\n", nowPlaying.SourceAccount)
|
||||
}
|
||||
|
||||
fmt.Printf(" Status: %s\n", nowPlaying.PlayStatus.String())
|
||||
}
|
||||
|
||||
@@ -56,9 +58,11 @@ func printTrackInfo(nowPlaying *models.NowPlaying) {
|
||||
if nowPlaying.Track != "" {
|
||||
fmt.Printf(" Track: %s\n", nowPlaying.Track)
|
||||
}
|
||||
|
||||
if nowPlaying.Artist != "" {
|
||||
fmt.Printf(" Artist: %s\n", nowPlaying.Artist)
|
||||
}
|
||||
|
||||
if nowPlaying.Album != "" {
|
||||
fmt.Printf(" Album: %s\n", nowPlaying.Album)
|
||||
}
|
||||
@@ -71,6 +75,7 @@ func printTimeInfo(nowPlaying *models.NowPlaying) {
|
||||
}
|
||||
|
||||
fmt.Printf(" Duration: %s\n", nowPlaying.FormatDuration())
|
||||
|
||||
if nowPlaying.Position != nil {
|
||||
fmt.Printf(" Position: %s\n", nowPlaying.FormatPosition())
|
||||
}
|
||||
@@ -115,9 +120,11 @@ func printVerboseContentInfo(nowPlaying *models.NowPlaying, verbose bool) {
|
||||
if nowPlaying.ContentItem.Type != "" {
|
||||
fmt.Printf(" Content Type: %s\n", nowPlaying.ContentItem.Type)
|
||||
}
|
||||
|
||||
if nowPlaying.ContentItem.ItemName != "" && nowPlaying.ContentItem.ItemName != nowPlaying.Track {
|
||||
fmt.Printf(" Item Name: %s\n", nowPlaying.ContentItem.ItemName)
|
||||
}
|
||||
|
||||
fmt.Printf(" Presetable: %t\n", nowPlaying.ContentItem.IsPresetable)
|
||||
}
|
||||
|
||||
|
||||
@@ -41,19 +41,24 @@ func storeCurrentPreset(c *cli.Context) error {
|
||||
PrintError("Current content cannot be saved as preset")
|
||||
fmt.Printf(" Content: %s\n", nowPlaying.Track)
|
||||
fmt.Printf(" Source: %s\n", nowPlaying.Source)
|
||||
|
||||
return fmt.Errorf("current content cannot be preset")
|
||||
}
|
||||
|
||||
// Show what we're about to store
|
||||
fmt.Printf("Current Content:\n")
|
||||
fmt.Printf(" Track: %s\n", nowPlaying.Track)
|
||||
|
||||
if nowPlaying.Artist != "" {
|
||||
fmt.Printf(" Artist: %s\n", nowPlaying.Artist)
|
||||
}
|
||||
|
||||
if nowPlaying.Album != "" {
|
||||
fmt.Printf(" Album: %s\n", nowPlaying.Album)
|
||||
}
|
||||
|
||||
fmt.Printf(" Source: %s\n", nowPlaying.Source)
|
||||
|
||||
if nowPlaying.ContentItem.Location != "" {
|
||||
fmt.Printf(" Location: %s\n", nowPlaying.ContentItem.Location)
|
||||
}
|
||||
@@ -105,14 +110,17 @@ func resolveLocationAndMetadata(params *presetParams) error {
|
||||
if params.name == "" {
|
||||
params.name = metadata.Name
|
||||
}
|
||||
|
||||
if params.artwork == "" {
|
||||
params.artwork = metadata.Artwork
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params.source = resolvedSource
|
||||
params.location = resolvedLocation
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -121,9 +129,11 @@ func validatePresetParams(params *presetParams) error {
|
||||
if params.source == "" {
|
||||
return fmt.Errorf("source is required (use --source)")
|
||||
}
|
||||
|
||||
if params.location == "" {
|
||||
return fmt.Errorf("location is required (use --location)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -160,9 +170,11 @@ func printPresetContent(params *presetParams) {
|
||||
fmt.Printf(" Name: %s\n", params.name)
|
||||
fmt.Printf(" Source: %s\n", params.source)
|
||||
fmt.Printf(" Location: %s\n", params.location)
|
||||
|
||||
if params.sourceAccount != "" {
|
||||
fmt.Printf(" Source Account: %s\n", params.sourceAccount)
|
||||
}
|
||||
|
||||
if params.itemType != "" {
|
||||
fmt.Printf(" Type: %s\n", params.itemType)
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ func listSources(c *cli.Context) error {
|
||||
|
||||
// Show service availability summary
|
||||
fmt.Println()
|
||||
|
||||
checker := NewServiceAvailabilityChecker(client)
|
||||
checker.PrintServiceAvailabilitySummary()
|
||||
|
||||
@@ -231,6 +232,7 @@ func getServiceAvailability(c *cli.Context) error {
|
||||
|
||||
// Show available services
|
||||
fmt.Printf("\n✅ Available Services:\n")
|
||||
|
||||
availableServices := serviceAvailability.GetAvailableServices()
|
||||
if len(availableServices) == 0 {
|
||||
fmt.Printf(" None\n")
|
||||
@@ -242,6 +244,7 @@ func getServiceAvailability(c *cli.Context) error {
|
||||
|
||||
// Show unavailable services with reasons
|
||||
fmt.Printf("\n❌ Unavailable Services:\n")
|
||||
|
||||
unavailableServices := serviceAvailability.GetUnavailableServices()
|
||||
if len(unavailableServices) == 0 {
|
||||
fmt.Printf(" None\n")
|
||||
@@ -251,35 +254,44 @@ func getServiceAvailability(c *cli.Context) error {
|
||||
if service.Reason != "" {
|
||||
reason = fmt.Sprintf(" (%s)", service.Reason)
|
||||
}
|
||||
|
||||
fmt.Printf(" • %s%s\n", formatServiceTypeForDisplay(models.ServiceType(service.Type)), reason)
|
||||
}
|
||||
}
|
||||
|
||||
// Show service categories
|
||||
fmt.Printf("\n🎵 Streaming Services:\n")
|
||||
|
||||
streamingServices := serviceAvailability.GetStreamingServices()
|
||||
availableCount := 0
|
||||
|
||||
for _, service := range streamingServices {
|
||||
status := "❌"
|
||||
if service.IsAvailable {
|
||||
status = "✅"
|
||||
availableCount++
|
||||
}
|
||||
|
||||
fmt.Printf(" %s %s\n", status, formatServiceTypeForDisplay(models.ServiceType(service.Type)))
|
||||
}
|
||||
|
||||
fmt.Printf(" Summary: %d/%d streaming services available\n", availableCount, len(streamingServices))
|
||||
|
||||
fmt.Printf("\n🔗 Local Input Services:\n")
|
||||
|
||||
localServices := serviceAvailability.GetLocalServices()
|
||||
localAvailableCount := 0
|
||||
|
||||
for _, service := range localServices {
|
||||
status := "❌"
|
||||
if service.IsAvailable {
|
||||
status = "✅"
|
||||
localAvailableCount++
|
||||
}
|
||||
|
||||
fmt.Printf(" %s %s\n", status, formatServiceTypeForDisplay(models.ServiceType(service.Type)))
|
||||
}
|
||||
|
||||
fmt.Printf(" Summary: %d/%d local services available\n", localAvailableCount, len(localServices))
|
||||
|
||||
return nil
|
||||
@@ -360,6 +372,7 @@ func compareServiceStatus(serviceName string, configured, available bool, servic
|
||||
default:
|
||||
fmt.Printf(" ➖ %s is neither configured nor available\n", serviceName)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
@@ -387,7 +400,6 @@ func printSourceSummary(sources *models.Sources, serviceAvailability *models.Ser
|
||||
fmt.Printf(" Ready configured sources: %d\n", sources.GetReadySourceCount())
|
||||
fmt.Printf(" Total available services: %d\n", serviceAvailability.GetAvailableServiceCount())
|
||||
fmt.Printf(" Total possible services: %d\n", serviceAvailability.GetServiceCount())
|
||||
|
||||
}
|
||||
|
||||
// boolToStatus converts boolean to user-friendly status
|
||||
@@ -395,5 +407,6 @@ func boolToStatus(b bool) string {
|
||||
if b {
|
||||
return "✅ Yes"
|
||||
}
|
||||
|
||||
return "❌ No"
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ func searchStations(c *cli.Context) error {
|
||||
}
|
||||
|
||||
printSearchResults(response, searchTerm)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -77,6 +78,7 @@ func searchTuneIn(c *cli.Context) error {
|
||||
}
|
||||
|
||||
printSearchResults(response, searchTerm)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -117,6 +119,7 @@ func searchPandora(c *cli.Context) error {
|
||||
}
|
||||
|
||||
printSearchResults(response, searchTerm)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -157,6 +160,7 @@ func searchSpotify(c *cli.Context) error {
|
||||
}
|
||||
|
||||
printSearchResults(response, searchTerm)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -206,6 +210,7 @@ func addStation(c *cli.Context) error {
|
||||
}
|
||||
|
||||
PrintSuccess(fmt.Sprintf("Added and started playing station: %s", name))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -250,6 +255,7 @@ func removeStation(c *cli.Context) error {
|
||||
}
|
||||
|
||||
PrintSuccess("Station removed successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -282,18 +288,23 @@ func printSongs(songs []models.SearchResult) {
|
||||
}
|
||||
|
||||
fmt.Printf("\n 🎵 Songs (%d):\n", len(songs))
|
||||
|
||||
for i := range songs {
|
||||
song := &songs[i]
|
||||
fmt.Printf(" %d. %s\n", i+1, song.GetDisplayName())
|
||||
|
||||
if song.Artist != "" {
|
||||
fmt.Printf(" Artist: %s\n", song.Artist)
|
||||
}
|
||||
|
||||
if song.Album != "" {
|
||||
fmt.Printf(" Album: %s\n", song.Album)
|
||||
}
|
||||
|
||||
if song.SourceAccount != "" {
|
||||
fmt.Printf(" Account: %s\n", song.SourceAccount)
|
||||
}
|
||||
|
||||
fmt.Printf(" Token: %s\n", song.Token)
|
||||
fmt.Println()
|
||||
}
|
||||
@@ -306,12 +317,15 @@ func printArtists(artists []models.SearchResult) {
|
||||
}
|
||||
|
||||
fmt.Printf(" 🎤 Artists (%d):\n", len(artists))
|
||||
|
||||
for i := range artists {
|
||||
artist := &artists[i]
|
||||
fmt.Printf(" %d. %s\n", i+1, artist.GetDisplayName())
|
||||
|
||||
if artist.SourceAccount != "" {
|
||||
fmt.Printf(" Account: %s\n", artist.SourceAccount)
|
||||
}
|
||||
|
||||
fmt.Printf(" Token: %s\n", artist.Token)
|
||||
fmt.Println()
|
||||
}
|
||||
@@ -324,16 +338,21 @@ func printStations(stations []models.SearchResult) {
|
||||
}
|
||||
|
||||
fmt.Printf(" 📻 Stations (%d):\n", len(stations))
|
||||
|
||||
for i := range stations {
|
||||
station := &stations[i]
|
||||
fmt.Printf(" %d. %s\n", i+1, station.GetDisplayName())
|
||||
|
||||
if station.SourceAccount != "" {
|
||||
fmt.Printf(" Account: %s\n", station.SourceAccount)
|
||||
}
|
||||
|
||||
fmt.Printf(" Token: %s\n", station.Token)
|
||||
|
||||
if station.Description != "" {
|
||||
fmt.Printf(" Description: %s\n", station.Description)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
@@ -342,9 +361,11 @@ func printStations(stations []models.SearchResult) {
|
||||
func printSearchHints(response *models.SearchStationResponse, songs, artists, stations []models.SearchResult) {
|
||||
fmt.Printf("💡 Usage hints:\n")
|
||||
fmt.Printf(" • To add a station and play it: station add --source %s --token <token> --name <name>\n", response.Source)
|
||||
|
||||
if hasAccountResults(response) {
|
||||
fmt.Printf(" • Include --source-account <account> when adding stations that require it\n")
|
||||
}
|
||||
|
||||
if len(songs) > 0 || len(artists) > 0 || len(stations) > 0 {
|
||||
fmt.Printf(" • Copy the token from results above to use with 'station add'\n")
|
||||
}
|
||||
@@ -358,6 +379,7 @@ func hasAccountResults(response *models.SearchStationResponse) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -393,6 +415,7 @@ func listStations(c *cli.Context) error {
|
||||
PrintError("Pandora source account is required")
|
||||
return fmt.Errorf("source account required for Pandora")
|
||||
}
|
||||
|
||||
response, err = client.GetPandoraStations(sourceAccount)
|
||||
default:
|
||||
return fmt.Errorf("listing stations is not supported for source: %s", source)
|
||||
@@ -404,6 +427,7 @@ func listStations(c *cli.Context) error {
|
||||
}
|
||||
|
||||
printStationList(response, source)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -427,9 +451,11 @@ func printStationList(response *models.NavigateResponse, source string) {
|
||||
if station.ContentItem.Location != "" {
|
||||
fmt.Printf(" Location: %s\n", station.ContentItem.Location)
|
||||
}
|
||||
|
||||
if station.ContentItem.SourceAccount != "" {
|
||||
fmt.Printf(" Account: %s\n", station.ContentItem.SourceAccount)
|
||||
}
|
||||
|
||||
if station.ContentItem.IsPresetable {
|
||||
fmt.Printf(" Can be saved as preset: Yes\n")
|
||||
}
|
||||
@@ -438,6 +464,7 @@ func printStationList(response *models.NavigateResponse, source string) {
|
||||
if station.Type != "" {
|
||||
fmt.Printf(" Type: %s\n", station.Type)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
|
||||
@@ -146,6 +146,7 @@ func resolveLocation(source, location string) (string, string) {
|
||||
// Example: https://tunein.com/radio/WDR-2-Rheinland-1004-s213886/
|
||||
if strings.Contains(location, "tunein.com/radio/") {
|
||||
trimmed := strings.TrimSuffix(location, "/")
|
||||
|
||||
parts := strings.Split(trimmed, "-")
|
||||
if len(parts) > 0 {
|
||||
lastPart := parts[len(parts)-1]
|
||||
@@ -155,6 +156,7 @@ func resolveLocation(source, location string) (string, string) {
|
||||
}
|
||||
// Fallback for URLs like https://tunein.com/radio/s213886/
|
||||
parts = strings.Split(trimmed, "/")
|
||||
|
||||
lastPart := parts[len(parts)-1]
|
||||
if strings.HasPrefix(lastPart, "s") {
|
||||
return "TUNEIN", "/v1/playback/station/" + lastPart
|
||||
@@ -203,6 +205,7 @@ func fetchTuneInMetadata(url string) (*TuneInMetadata, error) {
|
||||
titlePrefix := `property="og:title" content="`
|
||||
if idx := strings.Index(html, titlePrefix); idx != -1 {
|
||||
start := idx + len(titlePrefix)
|
||||
|
||||
end := strings.Index(html[start:], `"`)
|
||||
if end != -1 {
|
||||
title := html[start : start+end]
|
||||
@@ -210,9 +213,11 @@ func fetchTuneInMetadata(url string) (*TuneInMetadata, error) {
|
||||
if pipeIdx := strings.Index(title, " | "); pipeIdx != -1 {
|
||||
title = title[:pipeIdx]
|
||||
}
|
||||
|
||||
if commaIdx := strings.Index(title, ", "); commaIdx != -1 {
|
||||
title = title[:commaIdx]
|
||||
}
|
||||
|
||||
metadata.Name = title
|
||||
}
|
||||
}
|
||||
@@ -220,6 +225,7 @@ func fetchTuneInMetadata(url string) (*TuneInMetadata, error) {
|
||||
imagePrefix := `property="og:image" content="`
|
||||
if idx := strings.Index(html, imagePrefix); idx != -1 {
|
||||
start := idx + len(imagePrefix)
|
||||
|
||||
end := strings.Index(html[start:], `"`)
|
||||
if end != -1 {
|
||||
metadata.Artwork = html[start : start+end]
|
||||
|
||||
@@ -108,6 +108,7 @@ func TestResolveLocation(t *testing.T) {
|
||||
if gotSource != tt.expectedSource {
|
||||
t.Errorf("resolveLocation() gotSource = %v, want %v", gotSource, tt.expectedSource)
|
||||
}
|
||||
|
||||
if gotLocation != tt.expectedLocation {
|
||||
t.Errorf("resolveLocation() gotLocation = %v, want %v", gotLocation, tt.expectedLocation)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ func (sac *ServiceAvailabilityChecker) loadServiceAvailability() {
|
||||
// Create a mock availability that allows everything
|
||||
sac.serviceAvailability = &models.ServiceAvailability{}
|
||||
sac.cached = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -46,6 +47,7 @@ func (sac *ServiceAvailabilityChecker) loadServiceAvailability() {
|
||||
if err != nil {
|
||||
// If availability check fails, warn but don't fail the command
|
||||
PrintWarning(fmt.Sprintf("Could not check service availability: %v", err))
|
||||
|
||||
if !sac.skipAvailabilityCheck {
|
||||
PrintWarning("Command will proceed without availability validation")
|
||||
PrintWarning("Set SOUNDTOUCH_SKIP_AVAILABILITY_CHECK=true to disable these checks")
|
||||
@@ -53,6 +55,7 @@ func (sac *ServiceAvailabilityChecker) loadServiceAvailability() {
|
||||
// Create empty availability to prevent further errors
|
||||
sac.serviceAvailability = &models.ServiceAvailability{}
|
||||
sac.cached = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -94,6 +97,7 @@ func (sac *ServiceAvailabilityChecker) CheckServiceAvailable(serviceType models.
|
||||
sac.suggestAlternatives(serviceType, actionDescription)
|
||||
|
||||
PrintWarning("To bypass this check, set SOUNDTOUCH_SKIP_AVAILABILITY_CHECK=true")
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -241,6 +245,7 @@ func (sac *ServiceAvailabilityChecker) suggestAlternatives(serviceType models.Se
|
||||
if sac.serviceAvailability.HasTuneIn() {
|
||||
PrintWarning("💡 Alternative: TuneIn Radio is available for music streaming")
|
||||
}
|
||||
|
||||
if sac.serviceAvailability.HasPandora() {
|
||||
PrintWarning("💡 Alternative: Pandora is available for music streaming")
|
||||
}
|
||||
@@ -249,6 +254,7 @@ func (sac *ServiceAvailabilityChecker) suggestAlternatives(serviceType models.Se
|
||||
if sac.serviceAvailability.HasAirPlay() {
|
||||
PrintWarning("💡 Alternative: AirPlay is available for wireless audio")
|
||||
}
|
||||
|
||||
if sac.serviceAvailability.HasLocalMusic() {
|
||||
PrintWarning("💡 Alternative: Local Music Library is available")
|
||||
}
|
||||
@@ -257,6 +263,7 @@ func (sac *ServiceAvailabilityChecker) suggestAlternatives(serviceType models.Se
|
||||
if sac.serviceAvailability.HasSpotify() {
|
||||
PrintWarning("💡 Alternative: Spotify is available for music streaming")
|
||||
}
|
||||
|
||||
if sac.serviceAvailability.HasPandora() {
|
||||
PrintWarning("💡 Alternative: Pandora is available for music streaming")
|
||||
}
|
||||
@@ -358,6 +365,7 @@ func (sac *ServiceAvailabilityChecker) PrintServiceAvailabilitySummary() {
|
||||
|
||||
// Show quick status for popular services
|
||||
fmt.Printf(" Popular services:\n")
|
||||
|
||||
popularChecks := []struct {
|
||||
check func() bool
|
||||
name string
|
||||
@@ -374,6 +382,7 @@ func (sac *ServiceAvailabilityChecker) PrintServiceAvailabilitySummary() {
|
||||
if check.check() {
|
||||
status = "✅"
|
||||
}
|
||||
|
||||
fmt.Printf(" %s %s\n", status, check.name)
|
||||
}
|
||||
|
||||
|
||||
@@ -113,8 +113,10 @@ func displayServiceCategories(sa *models.ServiceAvailability) {
|
||||
status = "✅"
|
||||
availableCount++
|
||||
}
|
||||
|
||||
fmt.Printf(" %s %s\n", status, formatServiceName(service.Type))
|
||||
}
|
||||
|
||||
fmt.Printf(" Summary: %d/%d streaming services available\n", availableCount, len(streamingServices))
|
||||
|
||||
fmt.Println("\n🔗 LOCAL INPUT SERVICES:")
|
||||
|
||||
@@ -1441,9 +1441,11 @@ func (c *Client) Navigate(source, sourceAccount string, startItem, numItems int)
|
||||
if source == "" {
|
||||
return nil, fmt.Errorf("source cannot be empty")
|
||||
}
|
||||
|
||||
if startItem < 1 {
|
||||
return nil, fmt.Errorf("startItem must be >= 1, got %d", startItem)
|
||||
}
|
||||
|
||||
if numItems < 1 {
|
||||
return nil, fmt.Errorf("numItems must be >= 1, got %d", numItems)
|
||||
}
|
||||
@@ -1451,6 +1453,7 @@ func (c *Client) Navigate(source, sourceAccount string, startItem, numItems int)
|
||||
request := models.NewNavigateRequest(source, sourceAccount, startItem, numItems)
|
||||
|
||||
var response models.NavigateResponse
|
||||
|
||||
err := c.postWithResponse("/navigate", request, &response)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to navigate %s: %w", source, err)
|
||||
@@ -1464,9 +1467,11 @@ func (c *Client) NavigateWithMenu(source, sourceAccount, menu, sort string, star
|
||||
if source == "" {
|
||||
return nil, fmt.Errorf("source cannot be empty")
|
||||
}
|
||||
|
||||
if startItem < 1 {
|
||||
return nil, fmt.Errorf("startItem must be >= 1, got %d", startItem)
|
||||
}
|
||||
|
||||
if numItems < 1 {
|
||||
return nil, fmt.Errorf("numItems must be >= 1, got %d", numItems)
|
||||
}
|
||||
@@ -1474,6 +1479,7 @@ func (c *Client) NavigateWithMenu(source, sourceAccount, menu, sort string, star
|
||||
request := models.NewNavigateRequestWithMenu(source, sourceAccount, menu, sort, startItem, numItems)
|
||||
|
||||
var response models.NavigateResponse
|
||||
|
||||
err := c.postWithResponse("/navigate", request, &response)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to navigate %s with menu %s: %w", source, menu, err)
|
||||
@@ -1487,12 +1493,15 @@ func (c *Client) NavigateContainer(source, sourceAccount string, startItem, numI
|
||||
if source == "" {
|
||||
return nil, fmt.Errorf("source cannot be empty")
|
||||
}
|
||||
|
||||
if containerItem == nil {
|
||||
return nil, fmt.Errorf("container item cannot be nil")
|
||||
}
|
||||
|
||||
if startItem < 1 {
|
||||
return nil, fmt.Errorf("startItem must be >= 1, got %d", startItem)
|
||||
}
|
||||
|
||||
if numItems < 1 {
|
||||
return nil, fmt.Errorf("numItems must be >= 1, got %d", numItems)
|
||||
}
|
||||
@@ -1500,6 +1509,7 @@ func (c *Client) NavigateContainer(source, sourceAccount string, startItem, numI
|
||||
request := models.NewNavigateRequestWithItem(source, sourceAccount, startItem, numItems, containerItem)
|
||||
|
||||
var response models.NavigateResponse
|
||||
|
||||
err := c.postWithResponse("/navigate", request, &response)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to navigate container in %s: %w", source, err)
|
||||
@@ -1513,9 +1523,11 @@ func (c *Client) AddStation(source, sourceAccount, token, name string) error {
|
||||
if source == "" {
|
||||
return fmt.Errorf("source cannot be empty")
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
return fmt.Errorf("token cannot be empty")
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
return fmt.Errorf("station name cannot be empty")
|
||||
}
|
||||
@@ -1523,6 +1535,7 @@ func (c *Client) AddStation(source, sourceAccount, token, name string) error {
|
||||
request := models.NewAddStationRequest(source, sourceAccount, token, name)
|
||||
|
||||
var response models.StationResponse
|
||||
|
||||
err := c.postWithResponse("/addStation", request, &response)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add station '%s' to %s: %w", name, source, err)
|
||||
@@ -1536,14 +1549,17 @@ func (c *Client) RemoveStation(contentItem *models.ContentItem) error {
|
||||
if contentItem == nil {
|
||||
return fmt.Errorf("content item cannot be nil")
|
||||
}
|
||||
|
||||
if contentItem.Source == "" {
|
||||
return fmt.Errorf("content item source cannot be empty")
|
||||
}
|
||||
|
||||
if contentItem.Location == "" {
|
||||
return fmt.Errorf("content item location cannot be empty")
|
||||
}
|
||||
|
||||
var response models.StationResponse
|
||||
|
||||
err := c.postWithResponse("/removeStation", contentItem, &response)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove station from %s: %w", contentItem.Source, err)
|
||||
@@ -1580,6 +1596,7 @@ func (c *Client) SearchStation(source, sourceAccount, searchTerm string) (*model
|
||||
if source == "" {
|
||||
return nil, fmt.Errorf("source cannot be empty")
|
||||
}
|
||||
|
||||
if searchTerm == "" {
|
||||
return nil, fmt.Errorf("search term cannot be empty")
|
||||
}
|
||||
@@ -1587,6 +1604,7 @@ func (c *Client) SearchStation(source, sourceAccount, searchTerm string) (*model
|
||||
request := models.NewSearchStationRequest(source, sourceAccount, searchTerm)
|
||||
|
||||
var response models.SearchStationResponse
|
||||
|
||||
err := c.postWithResponse("/searchStation", request, &response)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to search stations in %s: %w", source, err)
|
||||
|
||||
@@ -40,6 +40,7 @@ func ExampleClient_SearchStation() {
|
||||
stations := results.GetStations()
|
||||
for _, station := range stations {
|
||||
fmt.Printf("Station: %s\n", station.GetDisplayName())
|
||||
|
||||
if station.Description != "" {
|
||||
fmt.Printf(" Description: %s\n", station.Description)
|
||||
}
|
||||
@@ -83,6 +84,7 @@ func Example_navigationWorkflow() {
|
||||
|
||||
// 1. Search for content
|
||||
fmt.Println("Searching for Taylor Swift...")
|
||||
|
||||
searchResults, err := client.SearchPandoraStations("user123", "Taylor Swift")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -111,6 +113,7 @@ func Example_navigationWorkflow() {
|
||||
|
||||
// 4. Browse existing Pandora stations
|
||||
fmt.Println("\nBrowsing existing Pandora stations...")
|
||||
|
||||
pandoraStations, err := client.GetPandoraStations("user123")
|
||||
if err != nil {
|
||||
fmt.Printf("Could not get Pandora stations: %v\n", err)
|
||||
@@ -173,9 +176,11 @@ func ExampleClient_NavigateContainer() {
|
||||
// Show first few tracks
|
||||
for i, track := range tracks[:minInt(3, len(tracks))] {
|
||||
fmt.Printf("%d. %s", i+1, track.GetDisplayName())
|
||||
|
||||
if track.ArtistName != "" {
|
||||
fmt.Printf(" - %s", track.ArtistName)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
@@ -205,9 +210,11 @@ func Example_searchAndPlayWorkflow() {
|
||||
|
||||
for i, station := range stations[:minInt(5, len(stations))] {
|
||||
fmt.Printf("%d. %s", i+1, station.GetDisplayName())
|
||||
|
||||
if station.Description != "" {
|
||||
fmt.Printf(" - %s", station.Description)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
@@ -230,5 +237,6 @@ func minInt(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ func TestClient_Navigation_Integration(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Logf("Navigate TUNEIN failed (may not be available): %v", err)
|
||||
t.Skip("TUNEIN not available on test device")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -68,6 +69,7 @@ func TestClient_Navigation_Integration(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Logf("GetTuneInStations failed (may not be available): %v", err)
|
||||
t.Skip("TuneIn not available on test device")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -86,6 +88,7 @@ func TestClient_Navigation_Integration(t *testing.T) {
|
||||
}
|
||||
|
||||
var storedMusicAccount string
|
||||
|
||||
for _, source := range sources.SourceItem {
|
||||
if source.Source == "STORED_MUSIC" && source.Status.IsReady() {
|
||||
storedMusicAccount = source.SourceAccount
|
||||
@@ -119,6 +122,7 @@ func TestClient_Navigation_Integration(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Logf("SearchTuneInStations failed (may not be supported): %v", err)
|
||||
t.Skip("TuneIn search not supported on test device")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -137,6 +141,7 @@ func TestClient_Navigation_Integration(t *testing.T) {
|
||||
if len(stations) > 0 {
|
||||
station := stations[0]
|
||||
t.Logf(" First station: %s", station.GetDisplayName())
|
||||
|
||||
if station.Token != "" {
|
||||
t.Logf(" Station token: %s", station.Token)
|
||||
}
|
||||
@@ -206,6 +211,7 @@ func TestClient_StationManagement_Integration(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Logf("SearchPandoraStations failed: %v", err)
|
||||
t.Skip("Pandora search not working")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -321,8 +327,10 @@ func TestClient_Navigation_ErrorHandling_Integration(t *testing.T) {
|
||||
var finalHost string
|
||||
|
||||
var finalPort int
|
||||
|
||||
if strings.Contains(host, ":") {
|
||||
parts := strings.Split(host, ":")
|
||||
|
||||
finalHost = parts[0]
|
||||
if len(parts) > 1 {
|
||||
finalPort = 8090
|
||||
@@ -398,8 +406,10 @@ func BenchmarkClient_Navigate_Integration(b *testing.B) {
|
||||
var finalHost string
|
||||
|
||||
var finalPort int
|
||||
|
||||
if strings.Contains(host, ":") {
|
||||
parts := strings.Split(host, ":")
|
||||
|
||||
finalHost = parts[0]
|
||||
if len(parts) > 1 {
|
||||
finalPort = 8090
|
||||
@@ -426,6 +436,7 @@ func BenchmarkClient_Navigate_Integration(b *testing.B) {
|
||||
if err != nil {
|
||||
b.Logf("Navigate failed: %v", err)
|
||||
b.Skip("TuneIn not available")
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -436,10 +447,12 @@ func BenchmarkClient_Navigate_Integration(b *testing.B) {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@ func TestClient_Navigate(t *testing.T) {
|
||||
if tt.serverStatus != 0 {
|
||||
w.WriteHeader(tt.serverStatus)
|
||||
}
|
||||
|
||||
if tt.serverResponse != "" {
|
||||
_, _ = w.Write([]byte(tt.serverResponse))
|
||||
}
|
||||
@@ -140,6 +141,7 @@ func TestClient_Navigate(t *testing.T) {
|
||||
} else if tt.errorContains != "" && !contains(err.Error(), tt.errorContains) {
|
||||
t.Errorf("Expected error to contain '%s', got: %v", tt.errorContains, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -178,6 +180,7 @@ func TestClient_NavigateWithMenu(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify request body contains menu and sort parameters
|
||||
var request models.NavigateRequest
|
||||
|
||||
err := xml.NewDecoder(r.Body).Decode(&request)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to decode request: %v", err)
|
||||
@@ -186,6 +189,7 @@ func TestClient_NavigateWithMenu(t *testing.T) {
|
||||
if request.Menu != "radioStations" {
|
||||
t.Errorf("Expected menu 'radioStations', got %s", request.Menu)
|
||||
}
|
||||
|
||||
if request.Sort != "dateCreated" {
|
||||
t.Errorf("Expected sort 'dateCreated', got %s", request.Sort)
|
||||
}
|
||||
@@ -202,8 +206,8 @@ func TestClient_NavigateWithMenu(t *testing.T) {
|
||||
}
|
||||
client := NewClient(config)
|
||||
client.baseURL = server.URL
|
||||
response, err := client.NavigateWithMenu("PANDORA", "user123", "radioStations", "dateCreated", 1, 100)
|
||||
|
||||
response, err := client.NavigateWithMenu("PANDORA", "user123", "radioStations", "dateCreated", 1, 100)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
@@ -212,6 +216,7 @@ func TestClient_NavigateWithMenu(t *testing.T) {
|
||||
if response.Source != "PANDORA" {
|
||||
t.Errorf("Expected source PANDORA, got %s", response.Source)
|
||||
}
|
||||
|
||||
if response.TotalItems != 5 {
|
||||
t.Errorf("Expected totalItems 5, got %d", response.TotalItems)
|
||||
}
|
||||
@@ -253,8 +258,8 @@ func TestClient_NavigateContainer(t *testing.T) {
|
||||
}
|
||||
client := NewClient(config)
|
||||
client.baseURL = server.URL
|
||||
response, err := client.NavigateContainer("STORED_MUSIC", "device123/0", 1, 1000, containerItem)
|
||||
|
||||
response, err := client.NavigateContainer("STORED_MUSIC", "device123/0", 1, 1000, containerItem)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
@@ -341,6 +346,7 @@ func TestClient_AddStation(t *testing.T) {
|
||||
if tt.serverStatus != 0 {
|
||||
w.WriteHeader(tt.serverStatus)
|
||||
}
|
||||
|
||||
if tt.serverResponse != "" {
|
||||
_, _ = w.Write([]byte(tt.serverResponse))
|
||||
}
|
||||
@@ -348,6 +354,7 @@ func TestClient_AddStation(t *testing.T) {
|
||||
// Verify request format
|
||||
if !tt.expectError {
|
||||
var request models.AddStationRequest
|
||||
|
||||
err := xml.NewDecoder(r.Body).Decode(&request)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to decode request: %v", err)
|
||||
@@ -356,9 +363,11 @@ func TestClient_AddStation(t *testing.T) {
|
||||
if request.Source != tt.source {
|
||||
t.Errorf("Expected source %s, got %s", tt.source, request.Source)
|
||||
}
|
||||
|
||||
if request.Token != tt.token {
|
||||
t.Errorf("Expected token %s, got %s", tt.token, request.Token)
|
||||
}
|
||||
|
||||
if request.Name != tt.stationName {
|
||||
t.Errorf("Expected name %s, got %s", tt.stationName, request.Name)
|
||||
}
|
||||
@@ -454,6 +463,7 @@ func TestClient_RemoveStation(t *testing.T) {
|
||||
if tt.serverStatus != 0 {
|
||||
w.WriteHeader(tt.serverStatus)
|
||||
}
|
||||
|
||||
if tt.serverResponse != "" {
|
||||
_, _ = w.Write([]byte(tt.serverResponse))
|
||||
}
|
||||
@@ -461,6 +471,7 @@ func TestClient_RemoveStation(t *testing.T) {
|
||||
// Verify request format
|
||||
if !tt.expectError && tt.contentItem != nil {
|
||||
var request models.ContentItem
|
||||
|
||||
err := xml.NewDecoder(r.Body).Decode(&request)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to decode request: %v", err)
|
||||
@@ -469,6 +480,7 @@ func TestClient_RemoveStation(t *testing.T) {
|
||||
if request.Source != tt.contentItem.Source {
|
||||
t.Errorf("Expected source %s, got %s", tt.contentItem.Source, request.Source)
|
||||
}
|
||||
|
||||
if request.Location != tt.contentItem.Location {
|
||||
t.Errorf("Expected location %s, got %s", tt.contentItem.Location, request.Location)
|
||||
}
|
||||
@@ -520,14 +532,17 @@ func TestClient_GetPandoraStations(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify it's calling navigate with the right parameters
|
||||
var request models.NavigateRequest
|
||||
|
||||
_ = xml.NewDecoder(r.Body).Decode(&request)
|
||||
|
||||
if request.Source != "PANDORA" {
|
||||
t.Errorf("Expected source PANDORA, got %s", request.Source)
|
||||
}
|
||||
|
||||
if request.Menu != "radioStations" {
|
||||
t.Errorf("Expected menu radioStations, got %s", request.Menu)
|
||||
}
|
||||
|
||||
if request.Sort != "dateCreated" {
|
||||
t.Errorf("Expected sort dateCreated, got %s", request.Sort)
|
||||
}
|
||||
@@ -544,8 +559,8 @@ func TestClient_GetPandoraStations(t *testing.T) {
|
||||
}
|
||||
client := NewClient(config)
|
||||
client.baseURL = server.URL
|
||||
response, err := client.GetPandoraStations("user123")
|
||||
|
||||
response, err := client.GetPandoraStations("user123")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
@@ -590,8 +605,8 @@ func TestClient_GetTuneInStations(t *testing.T) {
|
||||
}
|
||||
client := NewClient(config)
|
||||
client.baseURL = server.URL
|
||||
response, err := client.GetTuneInStations("Rock")
|
||||
|
||||
response, err := client.GetTuneInStations("Rock")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
@@ -630,8 +645,8 @@ func TestClient_GetStoredMusicLibrary(t *testing.T) {
|
||||
}
|
||||
client := NewClient(config)
|
||||
client.baseURL = server.URL
|
||||
response, err := client.GetStoredMusicLibrary("device123/0")
|
||||
|
||||
response, err := client.GetStoredMusicLibrary("device123/0")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
@@ -733,6 +748,7 @@ func TestClient_SearchStation(t *testing.T) {
|
||||
if tt.serverStatus != 0 {
|
||||
w.WriteHeader(tt.serverStatus)
|
||||
}
|
||||
|
||||
if tt.serverResponse != "" {
|
||||
_, _ = w.Write([]byte(tt.serverResponse))
|
||||
}
|
||||
@@ -740,6 +756,7 @@ func TestClient_SearchStation(t *testing.T) {
|
||||
// Verify request format for valid requests
|
||||
if !tt.expectError {
|
||||
var request models.SearchStationRequest
|
||||
|
||||
err := xml.NewDecoder(r.Body).Decode(&request)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to decode request: %v", err)
|
||||
@@ -748,6 +765,7 @@ func TestClient_SearchStation(t *testing.T) {
|
||||
if request.Source != tt.source {
|
||||
t.Errorf("Expected source %s, got %s", tt.source, request.Source)
|
||||
}
|
||||
|
||||
if request.SearchTerm != tt.searchTerm {
|
||||
t.Errorf("Expected searchTerm %s, got %s", tt.searchTerm, request.SearchTerm)
|
||||
}
|
||||
@@ -771,6 +789,7 @@ func TestClient_SearchStation(t *testing.T) {
|
||||
} else if tt.errorContains != "" && !contains(err.Error(), tt.errorContains) {
|
||||
t.Errorf("Expected error to contain '%s', got: %v", tt.errorContains, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -805,14 +824,17 @@ func TestClient_SearchPandoraStations(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify it's calling searchStation with the right parameters
|
||||
var request models.SearchStationRequest
|
||||
|
||||
_ = xml.NewDecoder(r.Body).Decode(&request)
|
||||
|
||||
if request.Source != "PANDORA" {
|
||||
t.Errorf("Expected source PANDORA, got %s", request.Source)
|
||||
}
|
||||
|
||||
if request.SourceAccount != "user123" {
|
||||
t.Errorf("Expected sourceAccount user123, got %s", request.SourceAccount)
|
||||
}
|
||||
|
||||
if request.SearchTerm != "Taylor Swift" {
|
||||
t.Errorf("Expected searchTerm 'Taylor Swift', got %s", request.SearchTerm)
|
||||
}
|
||||
@@ -829,8 +851,8 @@ func TestClient_SearchPandoraStations(t *testing.T) {
|
||||
}
|
||||
client := NewClient(config)
|
||||
client.baseURL = server.URL
|
||||
response, err := client.SearchPandoraStations("user123", "Taylor Swift")
|
||||
|
||||
response, err := client.SearchPandoraStations("user123", "Taylor Swift")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
@@ -872,8 +894,8 @@ func TestClient_SearchTuneInStations(t *testing.T) {
|
||||
}
|
||||
client := NewClient(config)
|
||||
client.baseURL = server.URL
|
||||
response, err := client.SearchTuneInStations("Jazz")
|
||||
|
||||
response, err := client.SearchTuneInStations("Jazz")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
@@ -914,8 +936,8 @@ func TestClient_SearchSpotifyContent(t *testing.T) {
|
||||
}
|
||||
client := NewClient(config)
|
||||
client.baseURL = server.URL
|
||||
response, err := client.SearchSpotifyContent("user@example.com", "Queen")
|
||||
|
||||
response, err := client.SearchSpotifyContent("user@example.com", "Queen")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
|
||||
@@ -50,8 +50,10 @@ func TestClient_NavigateXMLValidation(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var capturedXML string
|
||||
var capturedEndpoint string
|
||||
var (
|
||||
capturedXML string
|
||||
capturedEndpoint string
|
||||
)
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
capturedEndpoint = r.URL.Path
|
||||
@@ -277,9 +279,11 @@ func TestClient_RemoveStationXMLValidation(t *testing.T) {
|
||||
if !strings.Contains(capturedXML, `source="PANDORA"`) {
|
||||
t.Error("XML should contain source attribute")
|
||||
}
|
||||
|
||||
if !strings.Contains(capturedXML, `location="126740707481236361"`) {
|
||||
t.Error("XML should contain location attribute")
|
||||
}
|
||||
|
||||
if !strings.Contains(capturedXML, `<itemName>Test Station</itemName>`) {
|
||||
t.Error("XML should contain itemName element")
|
||||
}
|
||||
@@ -373,6 +377,7 @@ func TestClient_NavigationResponseParsing(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Error("Expected error but got none")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -410,9 +415,11 @@ func TestClient_NavigationResponseParsing(t *testing.T) {
|
||||
if !firstItem.IsPlayable() {
|
||||
t.Error("First item should be playable")
|
||||
}
|
||||
|
||||
if !firstItem.IsDirectory() {
|
||||
t.Error("First item should be directory")
|
||||
}
|
||||
|
||||
if firstItem.GetArtwork() == "" {
|
||||
t.Error("First item should have artwork")
|
||||
}
|
||||
@@ -421,6 +428,7 @@ func TestClient_NavigationResponseParsing(t *testing.T) {
|
||||
if !secondItem.IsTrack() {
|
||||
t.Error("Second item should be track")
|
||||
}
|
||||
|
||||
if secondItem.ArtistName != "Test Artist" {
|
||||
t.Errorf("Expected artist 'Test Artist', got %s", secondItem.ArtistName)
|
||||
}
|
||||
@@ -488,6 +496,7 @@ func TestClient_SearchStationResponseParsing(t *testing.T) {
|
||||
if response.DeviceID != "1004567890AA" {
|
||||
t.Errorf("Expected deviceID '1004567890AA', got %s", response.DeviceID)
|
||||
}
|
||||
|
||||
if response.Source != "PANDORA" {
|
||||
t.Errorf("Expected source PANDORA, got %s", response.Source)
|
||||
}
|
||||
@@ -518,6 +527,7 @@ func TestClient_SearchStationResponseParsing(t *testing.T) {
|
||||
if !song.IsSong() {
|
||||
t.Error("First result should be identified as song")
|
||||
}
|
||||
|
||||
if song.GetFullTitle() != "Old Church Choir - Zach Williams" {
|
||||
t.Errorf("Expected 'Old Church Choir - Zach Williams', got %s", song.GetFullTitle())
|
||||
}
|
||||
@@ -526,6 +536,7 @@ func TestClient_SearchStationResponseParsing(t *testing.T) {
|
||||
if !artist.IsArtist() {
|
||||
t.Error("Artist result should be identified as artist")
|
||||
}
|
||||
|
||||
if artist.GetDisplayName() != "Zach Williams" {
|
||||
t.Errorf("Expected 'Zach Williams', got %s", artist.GetDisplayName())
|
||||
}
|
||||
@@ -534,6 +545,7 @@ func TestClient_SearchStationResponseParsing(t *testing.T) {
|
||||
if !station.IsStation() {
|
||||
t.Error("Station result should be identified as station")
|
||||
}
|
||||
|
||||
if station.Description == "" {
|
||||
t.Error("Station should have description")
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ func TestClient_StorePreset(t *testing.T) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("Expected POST request, got %s", r.Method)
|
||||
}
|
||||
|
||||
if r.URL.Path != "/storePreset" {
|
||||
t.Errorf("Expected /storePreset endpoint, got %s", r.URL.Path)
|
||||
}
|
||||
@@ -116,6 +117,7 @@ func TestClient_StorePreset(t *testing.T) {
|
||||
if tt.serverStatus != 0 {
|
||||
w.WriteHeader(tt.serverStatus)
|
||||
}
|
||||
|
||||
if tt.serverResponse != "" {
|
||||
_, _ = w.Write([]byte(tt.serverResponse))
|
||||
}
|
||||
@@ -134,6 +136,7 @@ func TestClient_StorePreset(t *testing.T) {
|
||||
t.Errorf("Expected error, but got nil")
|
||||
return
|
||||
}
|
||||
|
||||
if tt.errorContains != "" && !strings.Contains(err.Error(), tt.errorContains) {
|
||||
t.Errorf("Expected error to contain '%s', but got: %v", tt.errorContains, err)
|
||||
}
|
||||
@@ -191,6 +194,7 @@ func TestClient_RemovePreset(t *testing.T) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("Expected POST request, got %s", r.Method)
|
||||
}
|
||||
|
||||
if r.URL.Path != "/removePreset" {
|
||||
t.Errorf("Expected /removePreset endpoint, got %s", r.URL.Path)
|
||||
}
|
||||
@@ -199,6 +203,7 @@ func TestClient_RemovePreset(t *testing.T) {
|
||||
if tt.serverStatus != 0 {
|
||||
w.WriteHeader(tt.serverStatus)
|
||||
}
|
||||
|
||||
if tt.serverResponse != "" {
|
||||
_, _ = w.Write([]byte(tt.serverResponse))
|
||||
}
|
||||
@@ -217,6 +222,7 @@ func TestClient_RemovePreset(t *testing.T) {
|
||||
t.Errorf("Expected error, but got nil")
|
||||
return
|
||||
}
|
||||
|
||||
if tt.errorContains != "" && !strings.Contains(err.Error(), tt.errorContains) {
|
||||
t.Errorf("Expected error to contain '%s', but got: %v", tt.errorContains, err)
|
||||
}
|
||||
@@ -342,6 +348,7 @@ func TestClient_StoreCurrentAsPreset(t *testing.T) {
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
if tt.nowPlayingResponse != "" {
|
||||
_, _ = w.Write([]byte(tt.nowPlayingResponse))
|
||||
}
|
||||
@@ -351,6 +358,7 @@ func TestClient_StoreCurrentAsPreset(t *testing.T) {
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
_, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?><presets></presets>`))
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
@@ -370,6 +378,7 @@ func TestClient_StoreCurrentAsPreset(t *testing.T) {
|
||||
t.Errorf("Expected error, but got nil")
|
||||
return
|
||||
}
|
||||
|
||||
if tt.errorContains != "" && !strings.Contains(err.Error(), tt.errorContains) {
|
||||
t.Errorf("Expected error to contain '%s', but got: %v", tt.errorContains, err)
|
||||
}
|
||||
@@ -384,6 +393,7 @@ func TestClient_StoreCurrentAsPreset(t *testing.T) {
|
||||
|
||||
func TestClient_StorePreset_XMLGeneration(t *testing.T) {
|
||||
var capturedXML string
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
body := make([]byte, r.ContentLength)
|
||||
_, _ = r.Body.Read(body)
|
||||
@@ -435,6 +445,7 @@ func TestClient_StorePreset_XMLGeneration(t *testing.T) {
|
||||
|
||||
func TestClient_RemovePreset_XMLGeneration(t *testing.T) {
|
||||
var capturedXML string
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
body := make([]byte, r.ContentLength)
|
||||
_, _ = r.Body.Read(body)
|
||||
|
||||
@@ -50,6 +50,7 @@ func TestGetServiceAvailability_Integration(t *testing.T) {
|
||||
status += " (" + service.Reason + ")"
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("Service %s: %s", service.Type, status)
|
||||
}
|
||||
}
|
||||
@@ -66,6 +67,7 @@ func TestGetServiceAvailability_Integration(t *testing.T) {
|
||||
// Test service categorization
|
||||
streamingServices := serviceAvailability.GetStreamingServices()
|
||||
t.Logf("Streaming services count: %d", len(streamingServices))
|
||||
|
||||
for _, service := range streamingServices {
|
||||
t.Logf(" - Streaming: %s (%v)", service.Type, service.IsAvailable)
|
||||
}
|
||||
@@ -181,6 +183,7 @@ func TestGetServiceAvailability_UserFeedback(t *testing.T) {
|
||||
availableServices := serviceAvailability.GetAvailableServices()
|
||||
if len(availableServices) > 0 {
|
||||
t.Log("\nAvailable Services:")
|
||||
|
||||
for _, service := range availableServices {
|
||||
t.Logf(" ✅ %s", formatServiceName(service.Type))
|
||||
}
|
||||
@@ -189,11 +192,13 @@ func TestGetServiceAvailability_UserFeedback(t *testing.T) {
|
||||
unavailableServices := serviceAvailability.GetUnavailableServices()
|
||||
if len(unavailableServices) > 0 {
|
||||
t.Log("\nUnavailable Services:")
|
||||
|
||||
for _, service := range unavailableServices {
|
||||
reason := ""
|
||||
if service.Reason != "" {
|
||||
reason = " - " + service.Reason
|
||||
}
|
||||
|
||||
t.Logf(" ❌ %s%s", formatServiceName(service.Type), reason)
|
||||
}
|
||||
}
|
||||
@@ -201,21 +206,25 @@ func TestGetServiceAvailability_UserFeedback(t *testing.T) {
|
||||
// Streaming services summary
|
||||
streamingServices := serviceAvailability.GetStreamingServices()
|
||||
availableStreaming := 0
|
||||
|
||||
for _, service := range streamingServices {
|
||||
if service.IsAvailable {
|
||||
availableStreaming++
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("\nStreaming Services: %d/%d available", availableStreaming, len(streamingServices))
|
||||
|
||||
// Local services summary
|
||||
localServices := serviceAvailability.GetLocalServices()
|
||||
availableLocal := 0
|
||||
|
||||
for _, service := range localServices {
|
||||
if service.IsAvailable {
|
||||
availableLocal++
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("Local Input Services: %d/%d available", availableLocal, len(localServices))
|
||||
|
||||
t.Log("\n=== END REPORT ===")
|
||||
|
||||
@@ -41,9 +41,11 @@ func TestGetServiceAvailability(t *testing.T) {
|
||||
expectError: false,
|
||||
validate: func(t *testing.T, sa *models.ServiceAvailability) {
|
||||
t.Helper()
|
||||
|
||||
if sa == nil {
|
||||
t.Fatal("service availability should not be nil")
|
||||
}
|
||||
|
||||
if sa.Services == nil {
|
||||
t.Fatal("services should not be nil")
|
||||
}
|
||||
@@ -67,21 +69,27 @@ func TestGetServiceAvailability(t *testing.T) {
|
||||
if !sa.HasSpotify() {
|
||||
t.Error("should have Spotify")
|
||||
}
|
||||
|
||||
if !sa.HasAirPlay() {
|
||||
t.Error("should have AirPlay")
|
||||
}
|
||||
|
||||
if !sa.HasTuneIn() {
|
||||
t.Error("should have TuneIn")
|
||||
}
|
||||
|
||||
if !sa.HasPandora() {
|
||||
t.Error("should have Pandora")
|
||||
}
|
||||
|
||||
if !sa.HasLocalMusic() {
|
||||
t.Error("should have Local Music")
|
||||
}
|
||||
|
||||
if sa.HasAlexa() {
|
||||
t.Error("should not have Alexa")
|
||||
}
|
||||
|
||||
if sa.HasBluetooth() {
|
||||
t.Error("should not have Bluetooth")
|
||||
}
|
||||
@@ -91,9 +99,11 @@ func TestGetServiceAvailability(t *testing.T) {
|
||||
if bluetoothService == nil {
|
||||
t.Fatal("bluetooth service should not be nil")
|
||||
}
|
||||
|
||||
if bluetoothService.IsAvailable {
|
||||
t.Error("bluetooth service should not be available")
|
||||
}
|
||||
|
||||
if bluetoothService.GetReason() != "INVALID_SOURCE_TYPE" {
|
||||
t.Errorf("expected bluetooth reason 'INVALID_SOURCE_TYPE', got '%s'", bluetoothService.GetReason())
|
||||
}
|
||||
@@ -125,9 +135,11 @@ func TestGetServiceAvailability(t *testing.T) {
|
||||
expectError: false,
|
||||
validate: func(t *testing.T, sa *models.ServiceAvailability) {
|
||||
t.Helper()
|
||||
|
||||
if sa == nil {
|
||||
t.Fatal("service availability should not be nil")
|
||||
}
|
||||
|
||||
if sa.Services == nil {
|
||||
t.Fatal("services should not be nil")
|
||||
}
|
||||
@@ -135,9 +147,11 @@ func TestGetServiceAvailability(t *testing.T) {
|
||||
if sa.GetServiceCount() != 3 {
|
||||
t.Errorf("expected 3 services, got %d", sa.GetServiceCount())
|
||||
}
|
||||
|
||||
if sa.GetAvailableServiceCount() != 3 {
|
||||
t.Errorf("expected 3 available services, got %d", sa.GetAvailableServiceCount())
|
||||
}
|
||||
|
||||
if sa.GetUnavailableServiceCount() != 0 {
|
||||
t.Errorf("expected 0 unavailable services, got %d", sa.GetUnavailableServiceCount())
|
||||
}
|
||||
@@ -145,9 +159,11 @@ func TestGetServiceAvailability(t *testing.T) {
|
||||
if !sa.HasSpotify() {
|
||||
t.Error("should have Spotify")
|
||||
}
|
||||
|
||||
if !sa.HasBluetooth() {
|
||||
t.Error("should have Bluetooth")
|
||||
}
|
||||
|
||||
if !sa.HasAirPlay() {
|
||||
t.Error("should have AirPlay")
|
||||
}
|
||||
@@ -164,9 +180,11 @@ func TestGetServiceAvailability(t *testing.T) {
|
||||
expectError: false,
|
||||
validate: func(t *testing.T, sa *models.ServiceAvailability) {
|
||||
t.Helper()
|
||||
|
||||
if sa == nil {
|
||||
t.Fatal("service availability should not be nil")
|
||||
}
|
||||
|
||||
if sa.Services == nil {
|
||||
t.Fatal("services should not be nil")
|
||||
}
|
||||
@@ -174,9 +192,11 @@ func TestGetServiceAvailability(t *testing.T) {
|
||||
if sa.GetServiceCount() != 0 {
|
||||
t.Errorf("expected 0 services, got %d", sa.GetServiceCount())
|
||||
}
|
||||
|
||||
if sa.GetAvailableServiceCount() != 0 {
|
||||
t.Errorf("expected 0 available services, got %d", sa.GetAvailableServiceCount())
|
||||
}
|
||||
|
||||
if sa.GetUnavailableServiceCount() != 0 {
|
||||
t.Errorf("expected 0 unavailable services, got %d", sa.GetUnavailableServiceCount())
|
||||
}
|
||||
@@ -184,6 +204,7 @@ func TestGetServiceAvailability(t *testing.T) {
|
||||
if sa.HasSpotify() {
|
||||
t.Error("should not have Spotify")
|
||||
}
|
||||
|
||||
if sa.HasBluetooth() {
|
||||
t.Error("should not have Bluetooth")
|
||||
}
|
||||
@@ -212,6 +233,7 @@ func TestGetServiceAvailability(t *testing.T) {
|
||||
if r.URL.Path != "/serviceAvailability" {
|
||||
t.Errorf("expected path /serviceAvailability, got %s", r.URL.Path)
|
||||
}
|
||||
|
||||
if r.Method != "GET" {
|
||||
t.Errorf("expected GET method, got %s", r.Method)
|
||||
}
|
||||
@@ -232,6 +254,7 @@ func TestGetServiceAvailability(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Error("expected an error but got none")
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
t.Error("expected nil result on error")
|
||||
}
|
||||
@@ -239,6 +262,7 @@ func TestGetServiceAvailability(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
tt.validate(t, result)
|
||||
}
|
||||
})
|
||||
@@ -250,13 +274,14 @@ func TestGetServiceAvailability_NetworkError(t *testing.T) {
|
||||
client := createTestClient("http://invalid-host:99999")
|
||||
|
||||
result, err := client.GetServiceAvailability()
|
||||
|
||||
if err == nil {
|
||||
t.Error("expected an error but got none")
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
t.Error("expected nil result on error")
|
||||
}
|
||||
|
||||
if err != nil && !contains(err.Error(), "failed to get service availability") {
|
||||
t.Errorf("error message should contain 'failed to get service availability', got: %v", err)
|
||||
}
|
||||
@@ -269,27 +294,35 @@ func TestServiceAvailabilityModel_EdgeCases(t *testing.T) {
|
||||
if sa.GetServiceCount() != 0 {
|
||||
t.Errorf("expected 0 service count, got %d", sa.GetServiceCount())
|
||||
}
|
||||
|
||||
if sa.GetAvailableServiceCount() != 0 {
|
||||
t.Errorf("expected 0 available count, got %d", sa.GetAvailableServiceCount())
|
||||
}
|
||||
|
||||
if sa.GetUnavailableServiceCount() != 0 {
|
||||
t.Errorf("expected 0 unavailable count, got %d", sa.GetUnavailableServiceCount())
|
||||
}
|
||||
|
||||
if sa.HasSpotify() {
|
||||
t.Error("should not have Spotify")
|
||||
}
|
||||
|
||||
if sa.GetServiceByType(models.ServiceTypeSpotify) != nil {
|
||||
t.Error("service should be nil")
|
||||
}
|
||||
|
||||
if len(sa.GetAvailableServices()) != 0 {
|
||||
t.Error("available services should be empty")
|
||||
}
|
||||
|
||||
if len(sa.GetUnavailableServices()) != 0 {
|
||||
t.Error("unavailable services should be empty")
|
||||
}
|
||||
|
||||
if len(sa.GetStreamingServices()) != 0 {
|
||||
t.Error("streaming services should be empty")
|
||||
}
|
||||
|
||||
if len(sa.GetLocalServices()) != 0 {
|
||||
t.Error("local services should be empty")
|
||||
}
|
||||
@@ -304,6 +337,7 @@ func TestServiceAvailabilityModel_EdgeCases(t *testing.T) {
|
||||
if !service.IsType(models.ServiceTypeSpotify) {
|
||||
t.Error("service should be of type Spotify")
|
||||
}
|
||||
|
||||
if service.IsType(models.ServiceTypeBluetooth) {
|
||||
t.Error("service should not be of type Bluetooth")
|
||||
}
|
||||
@@ -324,6 +358,7 @@ func TestServiceAvailabilityModel_EdgeCases(t *testing.T) {
|
||||
if serviceWithReason.GetReason() != "DEVICE_NOT_CONNECTED" {
|
||||
t.Errorf("expected DEVICE_NOT_CONNECTED, got %s", serviceWithReason.GetReason())
|
||||
}
|
||||
|
||||
if serviceWithoutReason.GetReason() != "" {
|
||||
t.Errorf("expected empty reason, got %s", serviceWithoutReason.GetReason())
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ func TestClient_GetSupportedURLs(t *testing.T) {
|
||||
if r.URL.Path != "/supportedURLs" {
|
||||
t.Errorf("Expected path '/supportedURLs', got '%s'", r.URL.Path)
|
||||
}
|
||||
|
||||
if r.Method != "GET" {
|
||||
t.Errorf("Expected GET method, got '%s'", r.Method)
|
||||
}
|
||||
@@ -124,6 +125,7 @@ func TestClient_GetSupportedURLs(t *testing.T) {
|
||||
if tt.expectedError && err == nil {
|
||||
t.Errorf("Expected error, but got none")
|
||||
}
|
||||
|
||||
if !tt.expectedError && err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
@@ -179,6 +181,7 @@ func TestClient_GetSupportedURLs_ServerError(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Error("Expected error for server error response, but got none")
|
||||
}
|
||||
|
||||
if supportedURLs != nil {
|
||||
t.Error("Expected nil supportedURLs on error, but got result")
|
||||
}
|
||||
@@ -207,6 +210,7 @@ func TestClient_GetSupportedURLs_NotFound(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Error("Expected error for 404 response, but got none")
|
||||
}
|
||||
|
||||
if supportedURLs != nil {
|
||||
t.Error("Expected nil supportedURLs on error, but got result")
|
||||
}
|
||||
@@ -236,6 +240,7 @@ func TestClient_GetSupportedURLs_InvalidXML(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Error("Expected error for invalid XML, but got none")
|
||||
}
|
||||
|
||||
if supportedURLs != nil {
|
||||
t.Error("Expected nil supportedURLs on error, but got result")
|
||||
}
|
||||
@@ -268,6 +273,7 @@ func TestSupportedURLsResponse_Methods(t *testing.T) {
|
||||
if len(urls) != 14 {
|
||||
t.Errorf("Expected 14 URLs, got %d", len(urls))
|
||||
}
|
||||
|
||||
if urls[0] != "/info" {
|
||||
t.Errorf("Expected first URL to be '/info', got '%s'", urls[0])
|
||||
}
|
||||
@@ -277,9 +283,11 @@ func TestSupportedURLsResponse_Methods(t *testing.T) {
|
||||
if !supportedURLs.HasURL("/info") {
|
||||
t.Error("Expected '/info' to be found")
|
||||
}
|
||||
|
||||
if !supportedURLs.HasURL("/capabilities") {
|
||||
t.Error("Expected '/capabilities' to be found")
|
||||
}
|
||||
|
||||
if supportedURLs.HasURL("/nonexistent") {
|
||||
t.Error("Expected '/nonexistent' not to be found")
|
||||
}
|
||||
@@ -294,18 +302,22 @@ func TestSupportedURLsResponse_Methods(t *testing.T) {
|
||||
|
||||
t.Run("GetCoreURLs", func(t *testing.T) {
|
||||
coreURLs := supportedURLs.GetCoreURLs()
|
||||
|
||||
expectedCore := []string{"/info", "/capabilities", "/sources", "/volume", "/bass", "/balance", "/presets", "/nowPlaying", "/key"}
|
||||
if len(coreURLs) != len(expectedCore) {
|
||||
t.Errorf("Expected %d core URLs, got %d", len(expectedCore), len(coreURLs))
|
||||
}
|
||||
|
||||
for _, url := range expectedCore {
|
||||
found := false
|
||||
|
||||
for _, core := range coreURLs {
|
||||
if core == url {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("Expected core URL '%s' not found", url)
|
||||
}
|
||||
@@ -314,6 +326,7 @@ func TestSupportedURLsResponse_Methods(t *testing.T) {
|
||||
|
||||
t.Run("GetStreamingURLs", func(t *testing.T) {
|
||||
streamingURLs := supportedURLs.GetStreamingURLs()
|
||||
|
||||
expectedStreaming := []string{"/navigate", "/search", "/sources"}
|
||||
if len(streamingURLs) != len(expectedStreaming) {
|
||||
t.Errorf("Expected %d streaming URLs, got %d", len(expectedStreaming), len(streamingURLs))
|
||||
@@ -322,6 +335,7 @@ func TestSupportedURLsResponse_Methods(t *testing.T) {
|
||||
|
||||
t.Run("GetAdvancedURLs", func(t *testing.T) {
|
||||
advancedURLs := supportedURLs.GetAdvancedURLs()
|
||||
|
||||
expectedAdvanced := []string{"/audiodspcontrols", "/setZone"}
|
||||
if len(advancedURLs) != len(expectedAdvanced) {
|
||||
t.Errorf("Expected %d advanced URLs, got %d", len(expectedAdvanced), len(advancedURLs))
|
||||
@@ -330,6 +344,7 @@ func TestSupportedURLsResponse_Methods(t *testing.T) {
|
||||
|
||||
t.Run("GetNetworkURLs", func(t *testing.T) {
|
||||
networkURLs := supportedURLs.GetNetworkURLs()
|
||||
|
||||
expectedNetwork := []string{"/networkInfo"}
|
||||
if len(networkURLs) != len(expectedNetwork) {
|
||||
t.Errorf("Expected %d network URLs, got %d", len(expectedNetwork), len(networkURLs))
|
||||
@@ -369,18 +384,22 @@ func TestSupportedURLsResponse_Methods(t *testing.T) {
|
||||
t.Run("GetUnsupportedURLs", func(t *testing.T) {
|
||||
checkList := []string{"/info", "/nonexistent1", "/capabilities", "/nonexistent2"}
|
||||
unsupported := supportedURLs.GetUnsupportedURLs(checkList)
|
||||
|
||||
expectedUnsupported := []string{"/nonexistent1", "/nonexistent2"}
|
||||
if len(unsupported) != len(expectedUnsupported) {
|
||||
t.Errorf("Expected %d unsupported URLs, got %d", len(expectedUnsupported), len(unsupported))
|
||||
}
|
||||
|
||||
for _, url := range expectedUnsupported {
|
||||
found := false
|
||||
|
||||
for _, unsup := range unsupported {
|
||||
if unsup == url {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("Expected unsupported URL '%s' not found", url)
|
||||
}
|
||||
@@ -399,12 +418,15 @@ func TestSupportedURLsResponse_EmptyURLs(t *testing.T) {
|
||||
if supportedURLs.GetURLCount() != 0 {
|
||||
t.Errorf("Expected 0 URLs, got %d", supportedURLs.GetURLCount())
|
||||
}
|
||||
|
||||
if supportedURLs.HasURL("/info") {
|
||||
t.Error("Expected '/info' not to be found in empty list")
|
||||
}
|
||||
|
||||
if supportedURLs.HasCorePlaybackSupport() {
|
||||
t.Error("Expected no core playback support with empty URLs")
|
||||
}
|
||||
|
||||
if supportedURLs.HasPresetSupport() {
|
||||
t.Error("Expected no preset support with empty URLs")
|
||||
}
|
||||
@@ -536,6 +558,7 @@ func TestSupportedURLsResponse_FeatureMapping(t *testing.T) {
|
||||
// With our comprehensive test data, should have no missing essential features
|
||||
if len(missing) > 0 {
|
||||
t.Errorf("Expected no missing essential features with comprehensive data, got %d", len(missing))
|
||||
|
||||
for _, feature := range missing {
|
||||
t.Errorf("Missing essential feature: %s", feature.Name)
|
||||
}
|
||||
@@ -602,15 +625,19 @@ func TestEndpointFeatureMap(t *testing.T) {
|
||||
if feature.Name == "" {
|
||||
t.Error("Feature should have a name")
|
||||
}
|
||||
|
||||
if feature.Description == "" {
|
||||
t.Error("Feature should have a description")
|
||||
}
|
||||
|
||||
if len(feature.Endpoints) == 0 {
|
||||
t.Errorf("Feature '%s' should have at least one endpoint", feature.Name)
|
||||
}
|
||||
|
||||
if feature.Category == "" {
|
||||
t.Errorf("Feature '%s' should have a category", feature.Name)
|
||||
}
|
||||
|
||||
if feature.CLICommand == "" {
|
||||
t.Errorf("Feature '%s' should have CLI command info", feature.Name)
|
||||
}
|
||||
@@ -633,6 +660,7 @@ func TestEndpointFeatureMap(t *testing.T) {
|
||||
|
||||
t.Run("EssentialFeatures", func(t *testing.T) {
|
||||
essentialCount := 0
|
||||
|
||||
for _, feature := range features {
|
||||
if feature.Essential {
|
||||
essentialCount++
|
||||
|
||||
@@ -268,6 +268,7 @@ func (sr *SearchStationResponse) GetAllResults() []SearchResult {
|
||||
allResults = append(allResults, sr.Songs...)
|
||||
allResults = append(allResults, sr.Artists...)
|
||||
allResults = append(allResults, sr.Stations...)
|
||||
|
||||
return allResults
|
||||
}
|
||||
|
||||
@@ -306,6 +307,7 @@ func (sr *SearchResult) GetDisplayName() string {
|
||||
if sr.Name != "" {
|
||||
return sr.Name
|
||||
}
|
||||
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
@@ -337,5 +339,6 @@ func (sr *SearchResult) GetFullTitle() string {
|
||||
if sr.Artist != "" {
|
||||
return sr.Name + " - " + sr.Artist
|
||||
}
|
||||
|
||||
return sr.Name
|
||||
}
|
||||
|
||||
@@ -15,9 +15,11 @@ func TestNavigateRequest_NewNavigateRequest(t *testing.T) {
|
||||
if req.SourceAccount != "user@example.com" {
|
||||
t.Errorf("Expected sourceAccount user@example.com, got %s", req.SourceAccount)
|
||||
}
|
||||
|
||||
if req.StartItem != 1 {
|
||||
t.Errorf("Expected startItem 1, got %d", req.StartItem)
|
||||
}
|
||||
|
||||
if req.NumItems != 50 {
|
||||
t.Errorf("Expected numItems 50, got %d", req.NumItems)
|
||||
}
|
||||
@@ -29,9 +31,11 @@ func TestNavigateRequest_NewNavigateRequestWithMenu(t *testing.T) {
|
||||
if req.Source != "PANDORA" {
|
||||
t.Errorf("Expected source PANDORA, got %s", req.Source)
|
||||
}
|
||||
|
||||
if req.Menu != "radioStations" {
|
||||
t.Errorf("Expected menu radioStations, got %s", req.Menu)
|
||||
}
|
||||
|
||||
if req.Sort != "dateCreated" {
|
||||
t.Errorf("Expected sort dateCreated, got %s", req.Sort)
|
||||
}
|
||||
@@ -99,6 +103,7 @@ func TestNavigateRequest_XMLMarshalWithItem(t *testing.T) {
|
||||
if !contains(xmlStr, `source="STORED_MUSIC"`) {
|
||||
t.Error("XML should contain source attribute")
|
||||
}
|
||||
|
||||
if !contains(xmlStr, `<startItem>1</startItem>`) {
|
||||
t.Error("XML should contain startItem element")
|
||||
}
|
||||
@@ -110,6 +115,7 @@ func TestNavigateRequest_XMLMarshalWithItem(t *testing.T) {
|
||||
if !contains(xmlStr, `<item`) {
|
||||
t.Error("XML should contain item element")
|
||||
}
|
||||
|
||||
if !contains(xmlStr, `<ContentItem`) {
|
||||
t.Error("XML should contain ContentItem element")
|
||||
}
|
||||
@@ -149,9 +155,11 @@ func TestNavigateResponse_XMLUnmarshal(t *testing.T) {
|
||||
if response.Source != "STORED_MUSIC" {
|
||||
t.Errorf("Expected source STORED_MUSIC, got %s", response.Source)
|
||||
}
|
||||
|
||||
if response.TotalItems != 2 {
|
||||
t.Errorf("Expected totalItems 2, got %d", response.TotalItems)
|
||||
}
|
||||
|
||||
if len(response.Items) != 2 {
|
||||
t.Errorf("Expected 2 items, got %d", len(response.Items))
|
||||
}
|
||||
@@ -161,12 +169,15 @@ func TestNavigateResponse_XMLUnmarshal(t *testing.T) {
|
||||
if firstItem.Name != "Album Artists" {
|
||||
t.Errorf("Expected first item name 'Album Artists', got %s", firstItem.Name)
|
||||
}
|
||||
|
||||
if firstItem.Type != "dir" {
|
||||
t.Errorf("Expected first item type 'dir', got %s", firstItem.Type)
|
||||
}
|
||||
|
||||
if !firstItem.IsPlayable() {
|
||||
t.Error("Expected first item to be playable")
|
||||
}
|
||||
|
||||
if !firstItem.IsDirectory() {
|
||||
t.Error("Expected first item to be a directory")
|
||||
}
|
||||
@@ -176,6 +187,7 @@ func TestNavigateResponse_XMLUnmarshal(t *testing.T) {
|
||||
if secondItem.Name != "Test Track" {
|
||||
t.Errorf("Expected second item name 'Test Track', got %s", secondItem.Name)
|
||||
}
|
||||
|
||||
if secondItem.ArtistName != "Test Artist" {
|
||||
t.Errorf("Expected artist name 'Test Artist', got %s", secondItem.ArtistName)
|
||||
}
|
||||
@@ -218,6 +230,7 @@ func TestNavigateResponse_FilterMethods(t *testing.T) {
|
||||
if len(directories) != 1 {
|
||||
t.Errorf("Expected 1 directory, got %d", len(directories))
|
||||
}
|
||||
|
||||
if directories[0].Name != "Directory 1" {
|
||||
t.Errorf("Expected directory name 'Directory 1', got %s", directories[0].Name)
|
||||
}
|
||||
@@ -227,6 +240,7 @@ func TestNavigateResponse_FilterMethods(t *testing.T) {
|
||||
if len(tracks) != 1 {
|
||||
t.Errorf("Expected 1 track, got %d", len(tracks))
|
||||
}
|
||||
|
||||
if tracks[0].Name != "Track 1" {
|
||||
t.Errorf("Expected track name 'Track 1', got %s", tracks[0].Name)
|
||||
}
|
||||
@@ -236,6 +250,7 @@ func TestNavigateResponse_FilterMethods(t *testing.T) {
|
||||
if len(stations) != 1 {
|
||||
t.Errorf("Expected 1 station, got %d", len(stations))
|
||||
}
|
||||
|
||||
if stations[0].Name != "Station 1" {
|
||||
t.Errorf("Expected station name 'Station 1', got %s", stations[0].Name)
|
||||
}
|
||||
@@ -388,9 +403,11 @@ func TestRemoveStationRequest(t *testing.T) {
|
||||
if !contains(xmlStr, `source="PANDORA"`) {
|
||||
t.Error("XML should contain source attribute")
|
||||
}
|
||||
|
||||
if !contains(xmlStr, `location="126740707481236361"`) {
|
||||
t.Error("XML should contain location attribute")
|
||||
}
|
||||
|
||||
if !contains(xmlStr, `<itemName>Test Station</itemName>`) {
|
||||
t.Error("XML should contain itemName element")
|
||||
}
|
||||
@@ -402,9 +419,11 @@ func TestSearchStationRequest_NewSearchStationRequest(t *testing.T) {
|
||||
if req.Source != "PANDORA" {
|
||||
t.Errorf("Expected source PANDORA, got %s", req.Source)
|
||||
}
|
||||
|
||||
if req.SourceAccount != "user123" {
|
||||
t.Errorf("Expected sourceAccount user123, got %s", req.SourceAccount)
|
||||
}
|
||||
|
||||
if req.SearchTerm != "Zach Williams" {
|
||||
t.Errorf("Expected searchTerm 'Zach Williams', got %s", req.SearchTerm)
|
||||
}
|
||||
@@ -483,15 +502,19 @@ func TestSearchStationResponse_XMLUnmarshal(t *testing.T) {
|
||||
if response.DeviceID != "1004567890AA" {
|
||||
t.Errorf("Expected deviceID '1004567890AA', got %s", response.DeviceID)
|
||||
}
|
||||
|
||||
if response.Source != "PANDORA" {
|
||||
t.Errorf("Expected source PANDORA, got %s", response.Source)
|
||||
}
|
||||
|
||||
if len(response.Songs) != 1 {
|
||||
t.Errorf("Expected 1 song result, got %d", len(response.Songs))
|
||||
}
|
||||
|
||||
if len(response.Artists) != 1 {
|
||||
t.Errorf("Expected 1 artist result, got %d", len(response.Artists))
|
||||
}
|
||||
|
||||
if len(response.Stations) != 1 {
|
||||
t.Errorf("Expected 1 station result, got %d", len(response.Stations))
|
||||
}
|
||||
@@ -501,9 +524,11 @@ func TestSearchStationResponse_XMLUnmarshal(t *testing.T) {
|
||||
if song.Name != "Old Church Choir" {
|
||||
t.Errorf("Expected song name 'Old Church Choir', got %s", song.Name)
|
||||
}
|
||||
|
||||
if song.Artist != "Zach Williams" {
|
||||
t.Errorf("Expected artist 'Zach Williams', got %s", song.Artist)
|
||||
}
|
||||
|
||||
if song.Token != "S10657777" {
|
||||
t.Errorf("Expected token 'S10657777', got %s", song.Token)
|
||||
}
|
||||
@@ -513,6 +538,7 @@ func TestSearchStationResponse_XMLUnmarshal(t *testing.T) {
|
||||
if artist.Name != "Zach Williams" {
|
||||
t.Errorf("Expected artist name 'Zach Williams', got %s", artist.Name)
|
||||
}
|
||||
|
||||
if !artist.IsArtist() {
|
||||
t.Error("Expected result to be identified as artist")
|
||||
}
|
||||
@@ -522,6 +548,7 @@ func TestSearchStationResponse_XMLUnmarshal(t *testing.T) {
|
||||
if station.Name != "Classic Rock Station" {
|
||||
t.Errorf("Expected station name 'Classic Rock Station', got %s", station.Name)
|
||||
}
|
||||
|
||||
if !station.IsStation() {
|
||||
t.Error("Expected result to be identified as station")
|
||||
}
|
||||
@@ -585,6 +612,7 @@ func TestSearchStationResponse_HelperMethods(t *testing.T) {
|
||||
if !emptyResponse.IsEmpty() {
|
||||
t.Error("Expected empty response to be empty")
|
||||
}
|
||||
|
||||
if emptyResponse.HasResults() {
|
||||
t.Error("Expected empty response to have no results")
|
||||
}
|
||||
@@ -630,6 +658,7 @@ func TestSearchResult_HelperMethods(t *testing.T) {
|
||||
if !tt.result.IsSong() {
|
||||
t.Error("Expected result to be identified as song")
|
||||
}
|
||||
|
||||
if tt.result.IsArtist() || tt.result.IsStation() {
|
||||
t.Error("Result incorrectly identified as artist or station")
|
||||
}
|
||||
@@ -637,6 +666,7 @@ func TestSearchResult_HelperMethods(t *testing.T) {
|
||||
if !tt.result.IsArtist() {
|
||||
t.Error("Expected result to be identified as artist")
|
||||
}
|
||||
|
||||
if tt.result.IsSong() || tt.result.IsStation() {
|
||||
t.Error("Result incorrectly identified as song or station")
|
||||
}
|
||||
@@ -644,6 +674,7 @@ func TestSearchResult_HelperMethods(t *testing.T) {
|
||||
if !tt.result.IsStation() {
|
||||
t.Error("Expected result to be identified as station")
|
||||
}
|
||||
|
||||
if tt.result.IsSong() || tt.result.IsArtist() {
|
||||
t.Error("Result incorrectly identified as song or artist")
|
||||
}
|
||||
@@ -685,5 +716,6 @@ func containsSubstring(s, substr string) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ func (sa *ServiceAvailability) GetAvailableServices() []Service {
|
||||
available = append(available, service)
|
||||
}
|
||||
}
|
||||
|
||||
return available
|
||||
}
|
||||
|
||||
@@ -85,11 +86,13 @@ func (sa *ServiceAvailability) GetUnavailableServices() []Service {
|
||||
}
|
||||
|
||||
var unavailable []Service
|
||||
|
||||
for _, service := range sa.Services.Service {
|
||||
if !service.IsAvailable {
|
||||
unavailable = append(unavailable, service)
|
||||
}
|
||||
}
|
||||
|
||||
return unavailable
|
||||
}
|
||||
|
||||
@@ -104,6 +107,7 @@ func (sa *ServiceAvailability) IsServiceAvailable(serviceType ServiceType) bool
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -118,6 +122,7 @@ func (sa *ServiceAvailability) GetServiceByType(serviceType ServiceType) *Servic
|
||||
return &service
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -182,6 +187,7 @@ func (sa *ServiceAvailability) GetStreamingServices() []Service {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return streaming
|
||||
}
|
||||
|
||||
@@ -198,6 +204,7 @@ func (sa *ServiceAvailability) GetLocalServices() []Service {
|
||||
}
|
||||
|
||||
var local []Service
|
||||
|
||||
for _, service := range sa.Services.Service {
|
||||
for _, localType := range localTypes {
|
||||
if service.Type == string(localType) {
|
||||
@@ -206,6 +213,7 @@ func (sa *ServiceAvailability) GetLocalServices() []Service {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return local
|
||||
}
|
||||
|
||||
@@ -214,6 +222,7 @@ func (sa *ServiceAvailability) GetServiceCount() int {
|
||||
if sa.Services == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return len(sa.Services.Service)
|
||||
}
|
||||
|
||||
|
||||
@@ -33,9 +33,11 @@ func TestServiceAvailability_UnmarshalXML(t *testing.T) {
|
||||
</serviceAvailability>`,
|
||||
validate: func(t *testing.T, sa *ServiceAvailability) {
|
||||
t.Helper()
|
||||
|
||||
if sa.Services == nil {
|
||||
t.Fatal("services should not be nil")
|
||||
}
|
||||
|
||||
if len(sa.Services.Service) != 13 {
|
||||
t.Errorf("expected 13 services, got %d", len(sa.Services.Service))
|
||||
}
|
||||
@@ -45,9 +47,11 @@ func TestServiceAvailability_UnmarshalXML(t *testing.T) {
|
||||
if spotifyService == nil {
|
||||
t.Fatal("spotify service should not be nil")
|
||||
}
|
||||
|
||||
if !spotifyService.IsAvailable {
|
||||
t.Error("spotify service should be available")
|
||||
}
|
||||
|
||||
if spotifyService.Reason != "" {
|
||||
t.Error("spotify service should not have a reason")
|
||||
}
|
||||
@@ -56,9 +60,11 @@ func TestServiceAvailability_UnmarshalXML(t *testing.T) {
|
||||
if bluetoothService == nil {
|
||||
t.Fatal("bluetooth service should not be nil")
|
||||
}
|
||||
|
||||
if bluetoothService.IsAvailable {
|
||||
t.Error("bluetooth service should not be available")
|
||||
}
|
||||
|
||||
if bluetoothService.Reason != "INVALID_SOURCE_TYPE" {
|
||||
t.Errorf("bluetooth service reason should be 'INVALID_SOURCE_TYPE', got '%s'", bluetoothService.Reason)
|
||||
}
|
||||
@@ -73,9 +79,11 @@ func TestServiceAvailability_UnmarshalXML(t *testing.T) {
|
||||
</serviceAvailability>`,
|
||||
validate: func(t *testing.T, sa *ServiceAvailability) {
|
||||
t.Helper()
|
||||
|
||||
if sa.Services == nil {
|
||||
t.Fatal("services should not be nil")
|
||||
}
|
||||
|
||||
if len(sa.Services.Service) != 0 {
|
||||
t.Errorf("expected 0 services, got %d", len(sa.Services.Service))
|
||||
}
|
||||
@@ -90,15 +98,19 @@ func TestServiceAvailability_UnmarshalXML(t *testing.T) {
|
||||
</serviceAvailability>`,
|
||||
validate: func(t *testing.T, sa *ServiceAvailability) {
|
||||
t.Helper()
|
||||
|
||||
if sa.Services == nil {
|
||||
t.Fatal("services should not be nil")
|
||||
}
|
||||
|
||||
if len(sa.Services.Service) != 1 {
|
||||
t.Errorf("expected 1 service, got %d", len(sa.Services.Service))
|
||||
}
|
||||
|
||||
if sa.Services.Service[0].Type != "SPOTIFY" {
|
||||
t.Errorf("expected SPOTIFY, got %s", sa.Services.Service[0].Type)
|
||||
}
|
||||
|
||||
if !sa.Services.Service[0].IsAvailable {
|
||||
t.Error("service should be available")
|
||||
}
|
||||
@@ -109,6 +121,7 @@ func TestServiceAvailability_UnmarshalXML(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var sa ServiceAvailability
|
||||
|
||||
err := xml.Unmarshal([]byte(tt.xmlData), &sa)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal XML: %v", err)
|
||||
@@ -135,9 +148,11 @@ func TestServiceAvailability_GetAvailableServices(t *testing.T) {
|
||||
if len(available) != 2 {
|
||||
t.Errorf("expected 2 available services, got %d", len(available))
|
||||
}
|
||||
|
||||
if available[0].Type != "SPOTIFY" {
|
||||
t.Errorf("expected first service to be SPOTIFY, got %s", available[0].Type)
|
||||
}
|
||||
|
||||
if available[1].Type != "AIRPLAY" {
|
||||
t.Errorf("expected second service to be AIRPLAY, got %s", available[1].Type)
|
||||
}
|
||||
@@ -159,9 +174,11 @@ func TestServiceAvailability_GetUnavailableServices(t *testing.T) {
|
||||
if len(unavailable) != 2 {
|
||||
t.Errorf("expected 2 unavailable services, got %d", len(unavailable))
|
||||
}
|
||||
|
||||
if unavailable[0].Type != "BLUETOOTH" {
|
||||
t.Errorf("expected first service to be BLUETOOTH, got %s", unavailable[0].Type)
|
||||
}
|
||||
|
||||
if unavailable[1].Type != "ALEXA" {
|
||||
t.Errorf("expected second service to be ALEXA, got %s", unavailable[1].Type)
|
||||
}
|
||||
@@ -180,9 +197,11 @@ func TestServiceAvailability_IsServiceAvailable(t *testing.T) {
|
||||
if !sa.IsServiceAvailable(ServiceTypeSpotify) {
|
||||
t.Error("Spotify should be available")
|
||||
}
|
||||
|
||||
if sa.IsServiceAvailable(ServiceTypeBluetooth) {
|
||||
t.Error("Bluetooth should not be available")
|
||||
}
|
||||
|
||||
if sa.IsServiceAvailable(ServiceTypeAlexa) {
|
||||
t.Error("Alexa should not be available (not in list)")
|
||||
}
|
||||
@@ -202,9 +221,11 @@ func TestServiceAvailability_GetServiceByType(t *testing.T) {
|
||||
if spotifyService == nil {
|
||||
t.Fatal("spotify service should not be nil")
|
||||
}
|
||||
|
||||
if spotifyService.Type != "SPOTIFY" {
|
||||
t.Errorf("expected SPOTIFY, got %s", spotifyService.Type)
|
||||
}
|
||||
|
||||
if !spotifyService.IsAvailable {
|
||||
t.Error("spotify service should be available")
|
||||
}
|
||||
@@ -213,12 +234,15 @@ func TestServiceAvailability_GetServiceByType(t *testing.T) {
|
||||
if bluetoothService == nil {
|
||||
t.Fatal("bluetooth service should not be nil")
|
||||
}
|
||||
|
||||
if bluetoothService.Type != "BLUETOOTH" {
|
||||
t.Errorf("expected BLUETOOTH, got %s", bluetoothService.Type)
|
||||
}
|
||||
|
||||
if bluetoothService.IsAvailable {
|
||||
t.Error("bluetooth service should not be available")
|
||||
}
|
||||
|
||||
if bluetoothService.Reason != "DEVICE_NOT_FOUND" {
|
||||
t.Errorf("expected DEVICE_NOT_FOUND, got %s", bluetoothService.Reason)
|
||||
}
|
||||
@@ -247,21 +271,27 @@ func TestServiceAvailability_ConvenienceMethods(t *testing.T) {
|
||||
if !sa.HasSpotify() {
|
||||
t.Error("should have Spotify")
|
||||
}
|
||||
|
||||
if sa.HasBluetooth() {
|
||||
t.Error("should not have Bluetooth")
|
||||
}
|
||||
|
||||
if !sa.HasAirPlay() {
|
||||
t.Error("should have AirPlay")
|
||||
}
|
||||
|
||||
if sa.HasAlexa() {
|
||||
t.Error("should not have Alexa")
|
||||
}
|
||||
|
||||
if !sa.HasTuneIn() {
|
||||
t.Error("should have TuneIn")
|
||||
}
|
||||
|
||||
if !sa.HasPandora() {
|
||||
t.Error("should have Pandora")
|
||||
}
|
||||
|
||||
if !sa.HasLocalMusic() {
|
||||
t.Error("should have Local Music")
|
||||
}
|
||||
@@ -362,9 +392,11 @@ func TestServiceAvailability_CountMethods(t *testing.T) {
|
||||
if sa.GetServiceCount() != 4 {
|
||||
t.Errorf("expected 4 total services, got %d", sa.GetServiceCount())
|
||||
}
|
||||
|
||||
if sa.GetAvailableServiceCount() != 2 {
|
||||
t.Errorf("expected 2 available services, got %d", sa.GetAvailableServiceCount())
|
||||
}
|
||||
|
||||
if sa.GetUnavailableServiceCount() != 2 {
|
||||
t.Errorf("expected 2 unavailable services, got %d", sa.GetUnavailableServiceCount())
|
||||
}
|
||||
@@ -376,33 +408,43 @@ func TestServiceAvailability_NilServicesHandling(t *testing.T) {
|
||||
if len(sa.GetAvailableServices()) != 0 {
|
||||
t.Error("available services should be empty")
|
||||
}
|
||||
|
||||
if len(sa.GetUnavailableServices()) != 0 {
|
||||
t.Error("unavailable services should be empty")
|
||||
}
|
||||
|
||||
if sa.IsServiceAvailable(ServiceTypeSpotify) {
|
||||
t.Error("Spotify should not be available")
|
||||
}
|
||||
|
||||
if sa.GetServiceByType(ServiceTypeSpotify) != nil {
|
||||
t.Error("service should be nil")
|
||||
}
|
||||
|
||||
if sa.HasSpotify() {
|
||||
t.Error("should not have Spotify")
|
||||
}
|
||||
|
||||
if sa.HasBluetooth() {
|
||||
t.Error("should not have Bluetooth")
|
||||
}
|
||||
|
||||
if len(sa.GetStreamingServices()) != 0 {
|
||||
t.Error("streaming services should be empty")
|
||||
}
|
||||
|
||||
if len(sa.GetLocalServices()) != 0 {
|
||||
t.Error("local services should be empty")
|
||||
}
|
||||
|
||||
if sa.GetServiceCount() != 0 {
|
||||
t.Error("service count should be 0")
|
||||
}
|
||||
|
||||
if sa.GetAvailableServiceCount() != 0 {
|
||||
t.Error("available service count should be 0")
|
||||
}
|
||||
|
||||
if sa.GetUnavailableServiceCount() != 0 {
|
||||
t.Error("unavailable service count should be 0")
|
||||
}
|
||||
@@ -414,6 +456,7 @@ func TestService_Methods(t *testing.T) {
|
||||
if !service.IsType(ServiceTypeSpotify) {
|
||||
t.Error("service should be of type Spotify")
|
||||
}
|
||||
|
||||
if service.IsType(ServiceTypeBluetooth) {
|
||||
t.Error("service should not be of type Bluetooth")
|
||||
}
|
||||
@@ -441,39 +484,51 @@ func TestServiceType_Constants(t *testing.T) {
|
||||
if ServiceTypeAirPlay != ServiceType("AIRPLAY") {
|
||||
t.Error("ServiceTypeAirPlay constant mismatch")
|
||||
}
|
||||
|
||||
if ServiceTypeAlexa != ServiceType("ALEXA") {
|
||||
t.Error("ServiceTypeAlexa constant mismatch")
|
||||
}
|
||||
|
||||
if ServiceTypeAmazon != ServiceType("AMAZON") {
|
||||
t.Error("ServiceTypeAmazon constant mismatch")
|
||||
}
|
||||
|
||||
if ServiceTypeBluetooth != ServiceType("BLUETOOTH") {
|
||||
t.Error("ServiceTypeBluetooth constant mismatch")
|
||||
}
|
||||
|
||||
if ServiceTypeBMX != ServiceType("BMX") {
|
||||
t.Error("ServiceTypeBMX constant mismatch")
|
||||
}
|
||||
|
||||
if ServiceTypeDeezer != ServiceType("DEEZER") {
|
||||
t.Error("ServiceTypeDeezer constant mismatch")
|
||||
}
|
||||
|
||||
if ServiceTypeIHeart != ServiceType("IHEART") {
|
||||
t.Error("ServiceTypeIHeart constant mismatch")
|
||||
}
|
||||
|
||||
if ServiceTypeLocalInternetRadio != ServiceType("LOCAL_INTERNET_RADIO") {
|
||||
t.Error("ServiceTypeLocalInternetRadio constant mismatch")
|
||||
}
|
||||
|
||||
if ServiceTypeLocalMusic != ServiceType("LOCAL_MUSIC") {
|
||||
t.Error("ServiceTypeLocalMusic constant mismatch")
|
||||
}
|
||||
|
||||
if ServiceTypeNotification != ServiceType("NOTIFICATION") {
|
||||
t.Error("ServiceTypeNotification constant mismatch")
|
||||
}
|
||||
|
||||
if ServiceTypePandora != ServiceType("PANDORA") {
|
||||
t.Error("ServiceTypePandora constant mismatch")
|
||||
}
|
||||
|
||||
if ServiceTypeSpotify != ServiceType("SPOTIFY") {
|
||||
t.Error("ServiceTypeSpotify constant mismatch")
|
||||
}
|
||||
|
||||
if ServiceTypeTuneIn != ServiceType("TUNEIN") {
|
||||
t.Error("ServiceTypeTuneIn constant mismatch")
|
||||
}
|
||||
@@ -496,6 +551,7 @@ func TestServiceAvailability_MarshalXML(t *testing.T) {
|
||||
|
||||
// Unmarshal back to verify roundtrip
|
||||
var unmarshaled ServiceAvailability
|
||||
|
||||
err = xml.Unmarshal(data, &unmarshaled)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to unmarshal XML: %v", err)
|
||||
@@ -504,9 +560,11 @@ func TestServiceAvailability_MarshalXML(t *testing.T) {
|
||||
if sa.GetServiceCount() != unmarshaled.GetServiceCount() {
|
||||
t.Error("service count mismatch after roundtrip")
|
||||
}
|
||||
|
||||
if sa.HasSpotify() != unmarshaled.HasSpotify() {
|
||||
t.Error("Spotify availability mismatch after roundtrip")
|
||||
}
|
||||
|
||||
if sa.HasBluetooth() != unmarshaled.HasBluetooth() {
|
||||
t.Error("Bluetooth availability mismatch after roundtrip")
|
||||
}
|
||||
@@ -515,6 +573,7 @@ func TestServiceAvailability_MarshalXML(t *testing.T) {
|
||||
if bluetoothService == nil {
|
||||
t.Fatal("bluetooth service should not be nil after roundtrip")
|
||||
}
|
||||
|
||||
if bluetoothService.Reason != "UNAVAILABLE" {
|
||||
t.Errorf("expected UNAVAILABLE reason, got %s", bluetoothService.Reason)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ func (s *SupportedURLsResponse) GetURLs() []string {
|
||||
for i, url := range s.URLs {
|
||||
urls[i] = url.Location
|
||||
}
|
||||
|
||||
return urls
|
||||
}
|
||||
|
||||
@@ -30,6 +31,7 @@ func (s *SupportedURLsResponse) HasURL(location string) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -47,11 +49,13 @@ func (s *SupportedURLsResponse) GetCoreURLs() []string {
|
||||
}
|
||||
|
||||
var available []string
|
||||
|
||||
for _, endpoint := range coreEndpoints {
|
||||
if s.HasURL(endpoint) {
|
||||
available = append(available, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
return available
|
||||
}
|
||||
|
||||
@@ -63,11 +67,13 @@ func (s *SupportedURLsResponse) GetStreamingURLs() []string {
|
||||
}
|
||||
|
||||
var available []string
|
||||
|
||||
for _, endpoint := range streamingEndpoints {
|
||||
if s.HasURL(endpoint) {
|
||||
available = append(available, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
return available
|
||||
}
|
||||
|
||||
@@ -81,11 +87,13 @@ func (s *SupportedURLsResponse) GetAdvancedURLs() []string {
|
||||
}
|
||||
|
||||
var available []string
|
||||
|
||||
for _, endpoint := range advancedEndpoints {
|
||||
if s.HasURL(endpoint) {
|
||||
available = append(available, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
return available
|
||||
}
|
||||
|
||||
@@ -97,11 +105,13 @@ func (s *SupportedURLsResponse) GetNetworkURLs() []string {
|
||||
}
|
||||
|
||||
var available []string
|
||||
|
||||
for _, endpoint := range networkEndpoints {
|
||||
if s.HasURL(endpoint) {
|
||||
available = append(available, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
return available
|
||||
}
|
||||
|
||||
@@ -113,6 +123,7 @@ func (s *SupportedURLsResponse) HasCorePlaybackSupport() bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -139,11 +150,13 @@ func (s *SupportedURLsResponse) HasStreamingSupport() bool {
|
||||
// GetUnsupportedURLs returns a list of common URLs that this device doesn't support
|
||||
func (s *SupportedURLsResponse) GetUnsupportedURLs(checkList []string) []string {
|
||||
var unsupported []string
|
||||
|
||||
for _, endpoint := range checkList {
|
||||
if !s.HasURL(endpoint) {
|
||||
unsupported = append(unsupported, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
return unsupported
|
||||
}
|
||||
|
||||
@@ -338,6 +351,7 @@ func (s *SupportedURLsResponse) GetFeaturesByCategory() map[string][]EndpointFea
|
||||
for _, feature := range features {
|
||||
// Check if device supports this feature (any of its endpoints)
|
||||
supported := false
|
||||
|
||||
for _, endpoint := range feature.Endpoints {
|
||||
if s.HasURL(endpoint) {
|
||||
supported = true
|
||||
@@ -362,6 +376,7 @@ func (s *SupportedURLsResponse) GetSupportedFeatures() []EndpointFeature {
|
||||
for _, feature := range features {
|
||||
// Check if device supports this feature (any of its endpoints)
|
||||
hasSupport := false
|
||||
|
||||
for _, endpoint := range feature.Endpoints {
|
||||
if s.HasURL(endpoint) {
|
||||
hasSupport = true
|
||||
@@ -386,6 +401,7 @@ func (s *SupportedURLsResponse) GetUnsupportedFeatures() []EndpointFeature {
|
||||
for _, feature := range features {
|
||||
// Check if device supports this feature (any of its endpoints)
|
||||
hasSupport := false
|
||||
|
||||
for _, endpoint := range feature.Endpoints {
|
||||
if s.HasURL(endpoint) {
|
||||
hasSupport = true
|
||||
@@ -413,6 +429,7 @@ func (s *SupportedURLsResponse) GetPartiallyImplementedFeatures() []EndpointFeat
|
||||
}
|
||||
|
||||
supportedCount := 0
|
||||
|
||||
for _, endpoint := range feature.Endpoints {
|
||||
if s.HasURL(endpoint) {
|
||||
supportedCount++
|
||||
@@ -441,6 +458,7 @@ func (s *SupportedURLsResponse) GetMissingEssentialFeatures() []EndpointFeature
|
||||
|
||||
// Check if device supports this essential feature
|
||||
hasSupport := false
|
||||
|
||||
for _, endpoint := range feature.Endpoints {
|
||||
if s.HasURL(endpoint) {
|
||||
hasSupport = true
|
||||
@@ -471,6 +489,7 @@ func (s *SupportedURLsResponse) GetFeatureCompleteness() (int, int, int) {
|
||||
|
||||
// Check if device supports this feature
|
||||
hasSupport := false
|
||||
|
||||
for _, endpoint := range feature.Endpoints {
|
||||
if s.HasURL(endpoint) {
|
||||
hasSupport = true
|
||||
|
||||
Reference in New Issue
Block a user