mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-19 00:56:16 +00:00
This commit completes the music service account management implementation and resolves all golangci-lint issues across the codebase. Music Service Account Management: • Add/remove accounts for all major streaming services (Spotify, Pandora, Amazon Music, Deezer, iHeartRadio) • Support for network music libraries (NAS/UPnP/DLNA servers) • Generic account management with service-specific convenience methods • Full CLI integration with 14 account management commands • Comprehensive test coverage with mock HTTP servers • Complete API documentation and usage examples New CLI Commands: • account list - List configured accounts • account add/remove - Generic account management • account add-spotify/remove-spotify - Spotify Premium • account add-pandora/remove-pandora - Pandora Music Service • account add-amazon/remove-amazon - Amazon Music • account add-deezer/remove-deezer - Deezer Premium • account add-iheart/remove-iheart - iHeartRadio • account add-nas/remove-nas - Network music libraries New API Methods: • SetMusicServiceAccount() / RemoveMusicServiceAccount() - Generic methods • AddSpotifyAccount() / RemoveSpotifyAccount() - Convenience methods • AddPandoraAccount() / RemovePandoraAccount() - Convenience methods • AddAmazonMusicAccount() / RemoveAmazonMusicAccount() - Convenience methods • AddDeezerAccount() / RemoveDeezerAccount() - Convenience methods • AddIHeartRadioAccount() / RemoveIHeartRadioAccount() - Convenience methods • AddStoredMusicAccount() / RemoveStoredMusicAccount() - Network libraries golangci-lint Fixes (36 issues resolved): • errcheck (3): Fixed unchecked w.Write() returns in tests • gocritic (3): Rewrote if-else chains to switch statements • gocyclo (6): Reduced cyclomatic complexity via helper function extraction • govet (12): Removed unused test data and field assignments • revive (6): Added package comments and fixed unused parameters • staticcheck (2): Replaced deprecated strings.Title usage • thelper (6): Added t.Helper() calls to test helper functions • unused (1): Removed unused createTestApp() function • whitespace/wsl_v5 (7): Fixed whitespace and formatting issues Code Quality Improvements: • All functions now have complexity < 15 (down from max 28) • Consistent error handling and validation patterns • Better separation of concerns with extracted helper functions • Zero external dependencies added for simple fixes • Comprehensive documentation with usage examples • Full backward compatibility maintained Files Added: • pkg/models/account.go - Account management models • pkg/models/account_test.go - Account model tests • pkg/client/account_test.go - Account client tests • cmd/soundtouch-cli/cmd_account.go - Account CLI commands • examples/account-management/ - Complete usage example • Updated docs/CLI-REFERENCE.md with account management section The implementation provides a complete, production-ready music service account management system with full CLI and programmatic API support.
620 lines
16 KiB
Go
620 lines
16 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"testing"
|
|
)
|
|
|
|
func TestRecentsResponse_Unmarshal(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
xmlData string
|
|
expected *RecentsResponse
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "complete recents response",
|
|
xmlData: `<recents>
|
|
<recent deviceID="1004567890AA" utcTime="1701202831">
|
|
<contentItem source="STORED_MUSIC" location="6_a2874b5d_4f83d999" sourceAccount="d09708a1-5953-44bc-a413-123456789012/0" isPresetable="true">
|
|
<itemName>MercyMe, It's Christmas!</itemName>
|
|
</contentItem>
|
|
</recent>
|
|
<recent deviceID="1004567890AA" utcTime="1700232917" id="2487503626">
|
|
<contentItem source="LOCAL_MUSIC" type="track" location="track:2590" sourceAccount="3f205110-4a57-4e91-810a-123456789012" isPresetable="true">
|
|
<itemName>Baby It's Cold Outside - ANNE MURRAY</itemName>
|
|
</contentItem>
|
|
</recent>
|
|
</recents>`,
|
|
expected: &RecentsResponse{
|
|
Items: []RecentsResponseItem{
|
|
{
|
|
DeviceID: "1004567890AA",
|
|
UTCTime: 1701202831,
|
|
ContentItem: &ContentItem{
|
|
Source: "STORED_MUSIC",
|
|
Location: "6_a2874b5d_4f83d999",
|
|
SourceAccount: "d09708a1-5953-44bc-a413-123456789012/0",
|
|
IsPresetable: true,
|
|
ItemName: "MercyMe, It's Christmas!",
|
|
},
|
|
},
|
|
{
|
|
DeviceID: "1004567890AA",
|
|
UTCTime: 1700232917,
|
|
ID: "2487503626",
|
|
ContentItem: &ContentItem{
|
|
Source: "LOCAL_MUSIC",
|
|
Type: "track",
|
|
Location: "track:2590",
|
|
SourceAccount: "3f205110-4a57-4e91-810a-123456789012",
|
|
IsPresetable: true,
|
|
ItemName: "Baby It's Cold Outside - ANNE MURRAY",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "spotify recent item",
|
|
xmlData: `<recents>
|
|
<recent deviceID="1004567890AA" utcTime="1701300000" id="spotify123">
|
|
<contentItem source="SPOTIFY" type="track" location="spotify:track:4iV5W9uYEdYUVa79Axb7Rh" sourceAccount="spotify_user" isPresetable="true">
|
|
<itemName>Shape of You - Ed Sheeran</itemName>
|
|
<containerArt>https://i.scdn.co/image/ab67616d0000b273ba5db46f4b838ef6027e6f96</containerArt>
|
|
</contentItem>
|
|
</recent>
|
|
</recents>`,
|
|
expected: &RecentsResponse{
|
|
Items: []RecentsResponseItem{
|
|
{
|
|
DeviceID: "1004567890AA",
|
|
UTCTime: 1701300000,
|
|
ID: "spotify123",
|
|
ContentItem: &ContentItem{
|
|
Source: "SPOTIFY",
|
|
Type: "track",
|
|
Location: "spotify:track:4iV5W9uYEdYUVa79Axb7Rh",
|
|
SourceAccount: "spotify_user",
|
|
IsPresetable: true,
|
|
ItemName: "Shape of You - Ed Sheeran",
|
|
ContainerArt: "https://i.scdn.co/image/ab67616d0000b273ba5db46f4b838ef6027e6f96",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "empty recents",
|
|
xmlData: `<recents>
|
|
</recents>`,
|
|
expected: &RecentsResponse{
|
|
Items: []RecentsResponseItem{},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var response RecentsResponse
|
|
|
|
err := xml.Unmarshal([]byte(tt.xmlData), &response)
|
|
|
|
if tt.expectError {
|
|
if err == nil {
|
|
t.Error("expected error, got nil")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Fatalf("failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
// Compare basic structure
|
|
if len(response.Items) != len(tt.expected.Items) {
|
|
t.Errorf("expected %d items, got %d", len(tt.expected.Items), len(response.Items))
|
|
}
|
|
|
|
// Compare each item
|
|
for i, expectedItem := range tt.expected.Items {
|
|
if i >= len(response.Items) {
|
|
break
|
|
}
|
|
|
|
actualItem := response.Items[i]
|
|
|
|
if actualItem.DeviceID != expectedItem.DeviceID {
|
|
t.Errorf("item %d: expected deviceID %s, got %s", i, expectedItem.DeviceID, actualItem.DeviceID)
|
|
}
|
|
|
|
if actualItem.UTCTime != expectedItem.UTCTime {
|
|
t.Errorf("item %d: expected utcTime %d, got %d", i, expectedItem.UTCTime, actualItem.UTCTime)
|
|
}
|
|
|
|
if actualItem.ID != expectedItem.ID {
|
|
t.Errorf("item %d: expected id %s, got %s", i, expectedItem.ID, actualItem.ID)
|
|
}
|
|
|
|
// Compare ContentItem
|
|
if expectedItem.ContentItem != nil {
|
|
if actualItem.ContentItem == nil {
|
|
t.Errorf("item %d: expected contentItem, got nil", i)
|
|
continue
|
|
}
|
|
|
|
if actualItem.ContentItem.Source != expectedItem.ContentItem.Source {
|
|
t.Errorf("item %d: expected source %s, got %s", i, expectedItem.ContentItem.Source, actualItem.ContentItem.Source)
|
|
}
|
|
|
|
if actualItem.ContentItem.Type != expectedItem.ContentItem.Type {
|
|
t.Errorf("item %d: expected type %s, got %s", i, expectedItem.ContentItem.Type, actualItem.ContentItem.Type)
|
|
}
|
|
|
|
if actualItem.ContentItem.Location != expectedItem.ContentItem.Location {
|
|
t.Errorf("item %d: expected location %s, got %s", i, expectedItem.ContentItem.Location, actualItem.ContentItem.Location)
|
|
}
|
|
|
|
if actualItem.ContentItem.ItemName != expectedItem.ContentItem.ItemName {
|
|
t.Errorf("item %d: expected itemName %s, got %s", i, expectedItem.ContentItem.ItemName, actualItem.ContentItem.ItemName)
|
|
}
|
|
|
|
if actualItem.ContentItem.IsPresetable != expectedItem.ContentItem.IsPresetable {
|
|
t.Errorf("item %d: expected isPresetable %t, got %t", i, expectedItem.ContentItem.IsPresetable, actualItem.ContentItem.IsPresetable)
|
|
}
|
|
} else if actualItem.ContentItem != nil {
|
|
t.Errorf("item %d: expected nil contentItem, got non-nil", i)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRecentsResponse_Methods(t *testing.T) {
|
|
response := &RecentsResponse{
|
|
Items: []RecentsResponseItem{
|
|
{
|
|
DeviceID: "device1",
|
|
UTCTime: 1701200000,
|
|
ContentItem: &ContentItem{
|
|
Source: "SPOTIFY",
|
|
Type: "track",
|
|
ItemName: "Song 1",
|
|
IsPresetable: true,
|
|
},
|
|
},
|
|
{
|
|
DeviceID: "device1",
|
|
UTCTime: 1701100000,
|
|
ContentItem: &ContentItem{
|
|
Source: "LOCAL_MUSIC",
|
|
Type: "track",
|
|
ItemName: "Song 2",
|
|
IsPresetable: false,
|
|
},
|
|
},
|
|
{
|
|
DeviceID: "device1",
|
|
UTCTime: 1701000000,
|
|
ContentItem: &ContentItem{
|
|
Source: "TUNEIN",
|
|
Type: "stationurl",
|
|
ItemName: "Radio Station",
|
|
IsPresetable: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Test GetItemCount
|
|
if response.GetItemCount() != 3 {
|
|
t.Errorf("expected item count 3, got %d", response.GetItemCount())
|
|
}
|
|
|
|
// Test IsEmpty
|
|
if response.IsEmpty() {
|
|
t.Error("expected IsEmpty() to return false")
|
|
}
|
|
|
|
// Test GetMostRecent
|
|
mostRecent := response.GetMostRecent()
|
|
if mostRecent == nil {
|
|
t.Error("expected most recent item, got nil")
|
|
} else if mostRecent.UTCTime != 1701200000 {
|
|
t.Errorf("expected most recent UTCTime 1701200000, got %d", mostRecent.UTCTime)
|
|
}
|
|
|
|
// Test GetItemsBySource
|
|
spotifyItems := response.GetItemsBySource("SPOTIFY")
|
|
if len(spotifyItems) != 1 {
|
|
t.Errorf("expected 1 Spotify item, got %d", len(spotifyItems))
|
|
}
|
|
|
|
localItems := response.GetItemsBySource("LOCAL_MUSIC")
|
|
if len(localItems) != 1 {
|
|
t.Errorf("expected 1 LOCAL_MUSIC item, got %d", len(localItems))
|
|
}
|
|
|
|
// Test GetSpotifyItems
|
|
spotifyItems2 := response.GetSpotifyItems()
|
|
if len(spotifyItems2) != 1 {
|
|
t.Errorf("expected 1 Spotify item, got %d", len(spotifyItems2))
|
|
}
|
|
|
|
// Test GetPresetableItems
|
|
presetableItems := response.GetPresetableItems()
|
|
if len(presetableItems) != 2 {
|
|
t.Errorf("expected 2 presetable items, got %d", len(presetableItems))
|
|
}
|
|
|
|
// Test GetTracks
|
|
tracks := response.GetTracks()
|
|
if len(tracks) != 2 {
|
|
t.Errorf("expected 2 track items, got %d", len(tracks))
|
|
}
|
|
|
|
// Test GetStations
|
|
stations := response.GetStations()
|
|
if len(stations) != 1 {
|
|
t.Errorf("expected 1 station item, got %d", len(stations))
|
|
}
|
|
}
|
|
|
|
func TestRecentsResponse_EmptyResponse(t *testing.T) {
|
|
response := &RecentsResponse{
|
|
Items: []RecentsResponseItem{},
|
|
}
|
|
|
|
// Test empty response methods
|
|
if response.GetItemCount() != 0 {
|
|
t.Errorf("expected item count 0, got %d", response.GetItemCount())
|
|
}
|
|
|
|
if !response.IsEmpty() {
|
|
t.Error("expected IsEmpty() to return true")
|
|
}
|
|
|
|
if response.GetMostRecent() != nil {
|
|
t.Error("expected GetMostRecent() to return nil")
|
|
}
|
|
|
|
if len(response.GetSpotifyItems()) != 0 {
|
|
t.Errorf("expected 0 Spotify items, got %d", len(response.GetSpotifyItems()))
|
|
}
|
|
}
|
|
|
|
func TestRecentItem_Methods(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
item RecentsResponseItem
|
|
test func(t *testing.T, item *RecentsResponseItem)
|
|
}{
|
|
{
|
|
name: "spotify track item",
|
|
item: RecentsResponseItem{
|
|
DeviceID: "device1",
|
|
UTCTime: 1701200000,
|
|
ID: "spotify123",
|
|
ContentItem: &ContentItem{
|
|
Source: "SPOTIFY",
|
|
Type: "track",
|
|
Location: "spotify:track:123",
|
|
SourceAccount: "user@spotify.com",
|
|
IsPresetable: true,
|
|
ItemName: "Test Song",
|
|
ContainerArt: "https://example.com/art.jpg",
|
|
},
|
|
},
|
|
test: func(t *testing.T, item *RecentsResponseItem) {
|
|
t.Helper()
|
|
|
|
if !item.HasContent() {
|
|
t.Error("expected HasContent() to return true")
|
|
}
|
|
|
|
if item.GetDisplayName() != "Test Song" {
|
|
t.Errorf("expected display name 'Test Song', got %s", item.GetDisplayName())
|
|
}
|
|
|
|
if item.GetSource() != "SPOTIFY" {
|
|
t.Errorf("expected source 'SPOTIFY', got %s", item.GetSource())
|
|
}
|
|
|
|
if !item.IsTrack() {
|
|
t.Error("expected IsTrack() to return true")
|
|
}
|
|
|
|
if !item.IsSpotifyContent() {
|
|
t.Error("expected IsSpotifyContent() to return true")
|
|
}
|
|
|
|
if !item.IsStreamingContent() {
|
|
t.Error("expected IsStreamingContent() to return true")
|
|
}
|
|
|
|
if item.IsLocalContent() {
|
|
t.Error("expected IsLocalContent() to return false")
|
|
}
|
|
|
|
if !item.IsPresetable() {
|
|
t.Error("expected IsPresetable() to return true")
|
|
}
|
|
|
|
if !item.HasArtwork() {
|
|
t.Error("expected HasArtwork() to return true")
|
|
}
|
|
|
|
if item.GetArtwork() != "https://example.com/art.jpg" {
|
|
t.Errorf("expected artwork URL, got %s", item.GetArtwork())
|
|
}
|
|
|
|
if item.GetUTCTime() != 1701200000 {
|
|
t.Errorf("expected UTC time 1701200000, got %d", item.GetUTCTime())
|
|
}
|
|
|
|
if !item.HasID() {
|
|
t.Error("expected HasID() to return true")
|
|
}
|
|
|
|
if item.GetID() != "spotify123" {
|
|
t.Errorf("expected ID 'spotify123', got %s", item.GetID())
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "local music item",
|
|
item: RecentsResponseItem{
|
|
DeviceID: "device1",
|
|
UTCTime: 1701100000,
|
|
ContentItem: &ContentItem{
|
|
Source: "LOCAL_MUSIC",
|
|
Type: "track",
|
|
Location: "/music/song.mp3",
|
|
IsPresetable: false,
|
|
ItemName: "Local Song",
|
|
},
|
|
},
|
|
test: func(t *testing.T, item *RecentsResponseItem) {
|
|
t.Helper()
|
|
|
|
if !item.IsLocalContent() {
|
|
t.Error("expected IsLocalContent() to return true")
|
|
}
|
|
|
|
if item.IsStreamingContent() {
|
|
t.Error("expected IsStreamingContent() to return false")
|
|
}
|
|
|
|
if item.IsSpotifyContent() {
|
|
t.Error("expected IsSpotifyContent() to return false")
|
|
}
|
|
|
|
if item.HasArtwork() {
|
|
t.Error("expected HasArtwork() to return false")
|
|
}
|
|
|
|
if item.GetArtwork() != "" {
|
|
t.Errorf("expected empty artwork, got %s", item.GetArtwork())
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "radio station item",
|
|
item: RecentsResponseItem{
|
|
DeviceID: "device1",
|
|
UTCTime: 1701000000,
|
|
ContentItem: &ContentItem{
|
|
Source: "TUNEIN",
|
|
Type: "stationurl",
|
|
Location: "tunein:station:123",
|
|
IsPresetable: true,
|
|
ItemName: "Rock FM",
|
|
},
|
|
},
|
|
test: func(t *testing.T, item *RecentsResponseItem) {
|
|
t.Helper()
|
|
|
|
if !item.IsStation() {
|
|
t.Error("expected IsStation() to return true")
|
|
}
|
|
|
|
if item.IsTrack() {
|
|
t.Error("expected IsTrack() to return false")
|
|
}
|
|
|
|
if !item.IsStreamingContent() {
|
|
t.Error("expected IsStreamingContent() to return true")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "empty content item",
|
|
item: RecentsResponseItem{
|
|
DeviceID: "device1",
|
|
UTCTime: 1701000000,
|
|
},
|
|
test: func(t *testing.T, item *RecentsResponseItem) {
|
|
t.Helper()
|
|
|
|
if item.HasContent() {
|
|
t.Error("expected HasContent() to return false")
|
|
}
|
|
|
|
if item.GetDisplayName() != "Unknown Item" {
|
|
t.Errorf("expected display name 'Unknown Item', got %s", item.GetDisplayName())
|
|
}
|
|
|
|
if item.GetSource() != "" {
|
|
t.Errorf("expected empty source, got %s", item.GetSource())
|
|
}
|
|
|
|
if item.IsTrack() {
|
|
t.Error("expected IsTrack() to return false")
|
|
}
|
|
|
|
if item.IsPresetable() {
|
|
t.Error("expected IsPresetable() to return false")
|
|
}
|
|
|
|
if item.HasID() {
|
|
t.Error("expected HasID() to return false")
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.test(t, &tt.item)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRecentItem_ContentTypes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
contentType string
|
|
expected map[string]bool
|
|
}{
|
|
{
|
|
name: "track type",
|
|
contentType: "track",
|
|
expected: map[string]bool{
|
|
"IsTrack": true,
|
|
"IsStation": false,
|
|
"IsPlaylist": false,
|
|
"IsAlbum": false,
|
|
"IsContainer": false,
|
|
},
|
|
},
|
|
{
|
|
name: "station type",
|
|
contentType: "stationurl",
|
|
expected: map[string]bool{
|
|
"IsTrack": false,
|
|
"IsStation": true,
|
|
"IsPlaylist": false,
|
|
"IsAlbum": false,
|
|
"IsContainer": false,
|
|
},
|
|
},
|
|
{
|
|
name: "playlist type",
|
|
contentType: "playlist",
|
|
expected: map[string]bool{
|
|
"IsTrack": false,
|
|
"IsStation": false,
|
|
"IsPlaylist": true,
|
|
"IsAlbum": false,
|
|
"IsContainer": false,
|
|
},
|
|
},
|
|
{
|
|
name: "album type",
|
|
contentType: "album",
|
|
expected: map[string]bool{
|
|
"IsTrack": false,
|
|
"IsStation": false,
|
|
"IsPlaylist": false,
|
|
"IsAlbum": true,
|
|
"IsContainer": false,
|
|
},
|
|
},
|
|
{
|
|
name: "container type",
|
|
contentType: "container",
|
|
expected: map[string]bool{
|
|
"IsTrack": false,
|
|
"IsStation": false,
|
|
"IsPlaylist": false,
|
|
"IsAlbum": false,
|
|
"IsContainer": true,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
item := RecentsResponseItem{
|
|
ContentItem: &ContentItem{
|
|
Type: tt.contentType,
|
|
},
|
|
}
|
|
|
|
results := map[string]bool{
|
|
"IsTrack": item.IsTrack(),
|
|
"IsStation": item.IsStation(),
|
|
"IsPlaylist": item.IsPlaylist(),
|
|
"IsAlbum": item.IsAlbum(),
|
|
"IsContainer": item.IsContainer(),
|
|
}
|
|
|
|
for method, expected := range tt.expected {
|
|
if results[method] != expected {
|
|
t.Errorf("expected %s() to return %t, got %t", method, expected, results[method])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRecentsResponse_FilterMethods(t *testing.T) {
|
|
response := &RecentsResponse{
|
|
Items: []RecentsResponseItem{
|
|
{
|
|
ContentItem: &ContentItem{Source: "SPOTIFY", Type: "track"},
|
|
},
|
|
{
|
|
ContentItem: &ContentItem{Source: "PANDORA", Type: "track"},
|
|
},
|
|
{
|
|
ContentItem: &ContentItem{Source: "LOCAL_MUSIC", Type: "track"},
|
|
},
|
|
{
|
|
ContentItem: &ContentItem{Source: "STORED_MUSIC", Type: "track"},
|
|
},
|
|
{
|
|
ContentItem: &ContentItem{Source: "TUNEIN", Type: "stationurl"},
|
|
},
|
|
{
|
|
ContentItem: &ContentItem{Source: "SPOTIFY", Type: "playlist"},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Test individual service filters
|
|
if len(response.GetSpotifyItems()) != 2 {
|
|
t.Errorf("expected 2 Spotify items, got %d", len(response.GetSpotifyItems()))
|
|
}
|
|
|
|
if len(response.GetPandoraItems()) != 1 {
|
|
t.Errorf("expected 1 Pandora item, got %d", len(response.GetPandoraItems()))
|
|
}
|
|
|
|
if len(response.GetLocalMusicItems()) != 1 {
|
|
t.Errorf("expected 1 LOCAL_MUSIC item, got %d", len(response.GetLocalMusicItems()))
|
|
}
|
|
|
|
if len(response.GetStoredMusicItems()) != 1 {
|
|
t.Errorf("expected 1 STORED_MUSIC item, got %d", len(response.GetStoredMusicItems()))
|
|
}
|
|
|
|
if len(response.GetTuneInItems()) != 1 {
|
|
t.Errorf("expected 1 TuneIn item, got %d", len(response.GetTuneInItems()))
|
|
}
|
|
|
|
// Test type filters
|
|
if len(response.GetTracks()) != 4 {
|
|
t.Errorf("expected 4 track items, got %d", len(response.GetTracks()))
|
|
}
|
|
|
|
if len(response.GetStations()) != 1 {
|
|
t.Errorf("expected 1 station item, got %d", len(response.GetStations()))
|
|
}
|
|
|
|
if len(response.GetPlaylistsAndAlbums()) != 1 {
|
|
t.Errorf("expected 1 playlist/album item, got %d", len(response.GetPlaylistsAndAlbums()))
|
|
}
|
|
}
|