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:
Tobias Gesellchen
2026-02-01 22:11:12 +01:00
parent f8f80a5121
commit 2768838bad
24 changed files with 399 additions and 12 deletions
+22
View File
@@ -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()
}
+10
View File
@@ -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)
}
+7
View File
@@ -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)
}
+12
View File
@@ -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)
}
+14 -1
View File
@@ -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"
}
+27
View File
@@ -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()
}
+6
View File
@@ -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]
+1
View File
@@ -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)
}
+2
View File
@@ -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:")
+18
View File
@@ -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)
+8
View File
@@ -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
}
+13
View File
@@ -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
}
}
+30 -8
View File
@@ -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
+14 -2
View File
@@ -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")
}
+11
View File
@@ -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 ===")
+36 -1
View File
@@ -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())
}
+28
View File
@@ -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++
+3
View File
@@ -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
}
+32
View File
@@ -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
}
+9
View File
@@ -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)
}
+59
View File
@@ -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)
}
+19
View File
@@ -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