package models import ( "encoding/xml" "testing" ) func TestNavigateRequest_NewNavigateRequest(t *testing.T) { req := NewNavigateRequest("SPOTIFY", "user@example.com", 1, 50) if req.Source != "SPOTIFY" { t.Errorf("Expected source SPOTIFY, got %s", req.Source) } 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) } } func TestNavigateRequest_NewNavigateRequestWithMenu(t *testing.T) { req := NewNavigateRequestWithMenu("PANDORA", "user123", "radioStations", "dateCreated", 1, 100) 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) } } func TestNavigateRequest_XMLMarshal(t *testing.T) { tests := []struct { name string request *NavigateRequest expected string }{ { name: "Basic navigate request", request: NewNavigateRequest("TUNEIN", "", 1, 25), expected: `125`, }, { name: "Navigate with source account", request: NewNavigateRequest("SPOTIFY", "user@example.com", 10, 50), expected: `1050`, }, { name: "Navigate with menu and sort", request: NewNavigateRequestWithMenu("PANDORA", "user123", "radioStations", "dateCreated", 1, 100), expected: `1100`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { xmlData, err := xml.Marshal(tt.request) if err != nil { t.Fatalf("Failed to marshal XML: %v", err) } actual := string(xmlData) if actual != tt.expected { t.Errorf("XML mismatch:\nExpected: %s\nActual: %s", tt.expected, actual) } }) } } func TestNavigateRequest_XMLMarshalWithItem(t *testing.T) { containerItem := &ContentItem{ Source: "STORED_MUSIC", Location: "1", SourceAccount: "device123/0", IsPresetable: true, ItemName: "Music", } request := NewNavigateRequestWithItem("STORED_MUSIC", "device123/0", 1, 1000, containerItem) xmlData, err := xml.Marshal(request) if err != nil { t.Fatalf("Failed to marshal XML: %v", err) } // Debug: print the actual XML to see what's generated xmlStr := string(xmlData) t.Logf("Generated XML: %s", xmlStr) // Check that the XML contains expected elements if !contains(xmlStr, `source="STORED_MUSIC"`) { t.Error("XML should contain source attribute") } if !contains(xmlStr, `1`) { t.Error("XML should contain startItem element") } if !contains(xmlStr, `1000`) { t.Error("XML should contain numItems element") } if !contains(xmlStr, ` 2 Album Artists dir Album Artists Test Track track Test Track Test Artist Test Album ` var response NavigateResponse err := xml.Unmarshal([]byte(xmlData), &response) if err != nil { t.Fatalf("Failed to unmarshal XML: %v", err) } 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)) } // Test first item (directory) firstItem := response.Items[0] 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") } // Test second item (track) secondItem := response.Items[1] 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) } if !secondItem.IsTrack() { t.Error("Expected second item to be a track") } } func TestNavigateResponse_FilterMethods(t *testing.T) { response := &NavigateResponse{ TotalItems: 3, Items: []NavigateItem{ { Name: "Directory 1", Type: "dir", Playable: 1, }, { Name: "Track 1", Type: "track", Playable: 1, }, { Name: "Station 1", Type: "stationurl", Playable: 1, }, }, } // Test GetPlayableItems playable := response.GetPlayableItems() if len(playable) != 3 { t.Errorf("Expected 3 playable items, got %d", len(playable)) } // Test GetDirectories directories := response.GetDirectories() 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) } // Test GetTracks tracks := response.GetTracks() 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) } // Test GetStations stations := response.GetStations() 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) } } func TestNavigateResponse_IsEmpty(t *testing.T) { tests := []struct { name string response *NavigateResponse expected bool }{ { name: "Empty response - no items", response: &NavigateResponse{TotalItems: 0, Items: []NavigateItem{}}, expected: true, }, { name: "Empty response - zero total", response: &NavigateResponse{TotalItems: 0, Items: []NavigateItem{{Name: "test"}}}, expected: true, }, { name: "Empty response - nil items", response: &NavigateResponse{TotalItems: 1, Items: nil}, expected: true, }, { name: "Non-empty response", response: &NavigateResponse{TotalItems: 1, Items: []NavigateItem{{Name: "test"}}}, expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if actual := tt.response.IsEmpty(); actual != tt.expected { t.Errorf("IsEmpty() = %v, expected %v", actual, tt.expected) } }) } } func TestNavigateItem_GetDisplayName(t *testing.T) { tests := []struct { name string item *NavigateItem expected string }{ { name: "Item with name", item: &NavigateItem{Name: "Test Item"}, expected: "Test Item", }, { name: "Item with ContentItem itemName", item: &NavigateItem{ContentItem: &ContentItem{ItemName: "Content Name"}}, expected: "Content Name", }, { name: "Item with both names - prefers item name", item: &NavigateItem{Name: "Item Name", ContentItem: &ContentItem{ItemName: "Content Name"}}, expected: "Item Name", }, { name: "Item with no names", item: &NavigateItem{}, expected: "Unknown Item", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if actual := tt.item.GetDisplayName(); actual != tt.expected { t.Errorf("GetDisplayName() = %s, expected %s", actual, tt.expected) } }) } } func TestAddStationRequest_XMLMarshal(t *testing.T) { req := NewAddStationRequest("PANDORA", "user123", "R4328162", "Test Station") xmlData, err := xml.Marshal(req) if err != nil { t.Fatalf("Failed to marshal XML: %v", err) } expected := `Test Station` actual := string(xmlData) if actual != expected { t.Errorf("XML mismatch:\nExpected: %s\nActual: %s", expected, actual) } } func TestAddStationRequest_XMLMarshalWithoutAccount(t *testing.T) { req := NewAddStationRequest("TUNEIN", "", "station123", "Radio Station") xmlData, err := xml.Marshal(req) if err != nil { t.Fatalf("Failed to marshal XML: %v", err) } expected := `Radio Station` actual := string(xmlData) if actual != expected { t.Errorf("XML mismatch:\nExpected: %s\nActual: %s", expected, actual) } } func TestStationResponse_XMLUnmarshal(t *testing.T) { xmlData := `/addStation` var response StationResponse err := xml.Unmarshal([]byte(xmlData), &response) if err != nil { t.Fatalf("Failed to unmarshal XML: %v", err) } if response.Status != "/addStation" { t.Errorf("Expected status '/addStation', got %s", response.Status) } } func TestRemoveStationRequest(t *testing.T) { contentItem := &ContentItem{ Source: "PANDORA", Location: "126740707481236361", SourceAccount: "user123", IsPresetable: true, ItemName: "Test Station", } req := NewRemoveStationRequest(contentItem) // Since RemoveStationRequest is just ContentItem, test that it's the same if req != contentItem { t.Error("NewRemoveStationRequest should return the same ContentItem") } // Test XML marshaling xmlData, err := xml.Marshal(req) if err != nil { t.Fatalf("Failed to marshal XML: %v", err) } xmlStr := string(xmlData) 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, `Test Station`) { t.Error("XML should contain itemName element") } } func TestSearchStationRequest_NewSearchStationRequest(t *testing.T) { req := NewSearchStationRequest("PANDORA", "user123", "Zach Williams") 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) } } func TestSearchStationRequest_XMLMarshal(t *testing.T) { tests := []struct { name string request *SearchStationRequest expected string }{ { name: "Basic search request", request: NewSearchStationRequest("PANDORA", "user123", "Classic Rock"), expected: `Classic Rock`, }, { name: "Search without source account", request: NewSearchStationRequest("TUNEIN", "", "Jazz"), expected: `Jazz`, }, { name: "Search with special characters", request: NewSearchStationRequest("SPOTIFY", "user@example.com", "Rock & Roll"), expected: `Rock & Roll`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { xmlData, err := xml.Marshal(tt.request) if err != nil { t.Fatalf("Failed to marshal XML: %v", err) } actual := string(xmlData) if actual != tt.expected { t.Errorf("XML mismatch:\nExpected: %s\nActual: %s", tt.expected, actual) } }) } } func TestSearchStationResponse_XMLUnmarshal(t *testing.T) { xmlData := ` Old Church Choir Zach Williams http://example.com/song.jpg Zach Williams http://example.com/artist.jpg Classic Rock Station The best classic rock hits http://example.com/station.jpg ` var response SearchStationResponse err := xml.Unmarshal([]byte(xmlData), &response) if err != nil { t.Fatalf("Failed to unmarshal XML: %v", err) } 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)) } // Test song result song := response.Songs[0] 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) } // Test artist result artist := response.Artists[0] 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") } // Test station result station := response.Stations[0] 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") } } func TestSearchStationResponse_HelperMethods(t *testing.T) { response := &SearchStationResponse{ Songs: []SearchResult{ {Name: "Song 1", Artist: "Artist 1", Token: "S1"}, {Name: "Song 2", Artist: "Artist 2", Token: "S2"}, }, Artists: []SearchResult{ {Name: "Artist 3", Token: "A1"}, }, Stations: []SearchResult{ {Name: "Station 1", Description: "Great music", Token: "R1"}, }, } // Test GetAllResults allResults := response.GetAllResults() if len(allResults) != 4 { t.Errorf("Expected 4 total results, got %d", len(allResults)) } // Test GetSongs songs := response.GetSongs() if len(songs) != 2 { t.Errorf("Expected 2 songs, got %d", len(songs)) } // Test GetArtists artists := response.GetArtists() if len(artists) != 1 { t.Errorf("Expected 1 artist, got %d", len(artists)) } // Test GetStations stations := response.GetStations() if len(stations) != 1 { t.Errorf("Expected 1 station, got %d", len(stations)) } // Test HasResults if !response.HasResults() { t.Error("Expected response to have results") } // Test GetResultCount if response.GetResultCount() != 4 { t.Errorf("Expected result count 4, got %d", response.GetResultCount()) } // Test IsEmpty if response.IsEmpty() { t.Error("Expected response not to be empty") } // Test empty response emptyResponse := &SearchStationResponse{} if !emptyResponse.IsEmpty() { t.Error("Expected empty response to be empty") } if emptyResponse.HasResults() { t.Error("Expected empty response to have no results") } } func TestSearchResult_HelperMethods(t *testing.T) { tests := []struct { name string result SearchResult expectedType string fullTitle string }{ { name: "Song result", result: SearchResult{Name: "Test Song", Artist: "Test Artist"}, expectedType: "song", fullTitle: "Test Song - Test Artist", }, { name: "Artist result", result: SearchResult{Name: "Test Artist"}, expectedType: "artist", fullTitle: "Test Artist", }, { name: "Station result", result: SearchResult{Name: "Test Station", Description: "Great station"}, expectedType: "station", fullTitle: "Test Station", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Test GetDisplayName if tt.result.GetDisplayName() != tt.result.Name { t.Errorf("Expected display name %s, got %s", tt.result.Name, tt.result.GetDisplayName()) } // Test type detection switch tt.expectedType { case "song": 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") } case "artist": 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") } case "station": 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") } } // Test GetFullTitle if tt.result.GetFullTitle() != tt.fullTitle { t.Errorf("Expected full title %s, got %s", tt.fullTitle, tt.result.GetFullTitle()) } }) } } func TestSearchResult_GetArtworkURL(t *testing.T) { result := SearchResult{ Name: "Test", Logo: "http://example.com/artwork.jpg", } if result.GetArtworkURL() != "http://example.com/artwork.jpg" { t.Errorf("Expected artwork URL 'http://example.com/artwork.jpg', got %s", result.GetArtworkURL()) } // Test empty logo emptyResult := SearchResult{Name: "Test"} if emptyResult.GetArtworkURL() != "" { t.Errorf("Expected empty artwork URL, got %s", emptyResult.GetArtworkURL()) } } // Helper function to check if string contains substring func contains(s, substr string) bool { return len(s) >= len(substr) && (s == substr || len(substr) == 0 || (len(s) > len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr || containsSubstring(s, substr)))) } func containsSubstring(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }