mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 16:46:17 +00:00
- Fix content type display tests to expect lowercase 'track' instead of 'Track' - Content types should display raw API values for technical accuracy - Fix icon test to expect correct emoji for unknown content types - Update documentation links in README files: - Point source selection links to docs/SOURCE-SELECTION.md - Point navigation links to docs/NAVIGATION-GUIDE.md - Point zone management links to docs/zone-management.md - Update service management link to SERVICE-AVAILABILITY-IMPLEMENTATION.md All tests now pass and documentation links are verified to exist.
412 lines
9.5 KiB
Go
412 lines
9.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/gesellix/bose-soundtouch/pkg/models"
|
|
)
|
|
|
|
func TestRecentsCommands(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
expectedOutput []string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "recents list command",
|
|
args: []string{"soundtouch-cli", "--host", "192.168.1.100", "recents", "list"},
|
|
expectedOutput: []string{
|
|
"Getting recently played content",
|
|
"Recent Items Summary:",
|
|
"Recent Items",
|
|
},
|
|
},
|
|
{
|
|
name: "recents filter by source",
|
|
args: []string{"soundtouch-cli", "--host", "192.168.1.100", "recents", "filter", "--source", "SPOTIFY"},
|
|
expectedOutput: []string{
|
|
"Getting filtered recent content",
|
|
"filtered by source: SPOTIFY",
|
|
},
|
|
},
|
|
{
|
|
name: "recents latest command",
|
|
args: []string{"soundtouch-cli", "--host", "192.168.1.100", "recents", "latest"},
|
|
expectedOutput: []string{
|
|
"Getting most recent item",
|
|
"Most Recent Item:",
|
|
},
|
|
},
|
|
{
|
|
name: "recents stats command",
|
|
args: []string{"soundtouch-cli", "--host", "192.168.1.100", "recents", "stats"},
|
|
expectedOutput: []string{
|
|
"Getting recent items statistics",
|
|
"Recent Items Statistics",
|
|
},
|
|
},
|
|
{
|
|
name: "recents missing host",
|
|
args: []string{"soundtouch-cli", "recents", "list"},
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Skip actual execution for now - these would need mock HTTP servers
|
|
// This test structure shows how the CLI commands would be tested
|
|
t.Skip("Integration test - requires mock HTTP server setup")
|
|
|
|
// Example of how you would set up the test:
|
|
// app := createTestApp()
|
|
//
|
|
// var buf bytes.Buffer
|
|
// app.Writer = &buf
|
|
// app.ErrWriter = &buf
|
|
//
|
|
// err := app.Run(tt.args)
|
|
//
|
|
// if tt.expectError {
|
|
// if err == nil {
|
|
// t.Error("expected error, got nil")
|
|
// }
|
|
// return
|
|
// }
|
|
//
|
|
// if err != nil {
|
|
// t.Fatalf("unexpected error: %v", err)
|
|
// }
|
|
//
|
|
// output := buf.String()
|
|
// for _, expected := range tt.expectedOutput {
|
|
// if !strings.Contains(output, expected) {
|
|
// t.Errorf("expected output to contain %q, got:\n%s", expected, output)
|
|
// }
|
|
// }
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPrintRecentItem(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
item *models.RecentsResponseItem
|
|
detailed bool
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "basic track item",
|
|
item: &models.RecentsResponseItem{
|
|
DeviceID: "device1",
|
|
UTCTime: 1701200000,
|
|
ContentItem: &models.ContentItem{
|
|
Source: "SPOTIFY",
|
|
Type: "track",
|
|
ItemName: "Test Song",
|
|
},
|
|
},
|
|
detailed: false,
|
|
expected: []string{
|
|
"🎵 Test Song",
|
|
"Source: Spotify",
|
|
"Type: track",
|
|
},
|
|
},
|
|
{
|
|
name: "detailed station item",
|
|
item: &models.RecentsResponseItem{
|
|
DeviceID: "device1",
|
|
UTCTime: 1701200000,
|
|
ID: "station123",
|
|
ContentItem: &models.ContentItem{
|
|
Source: "TUNEIN",
|
|
Type: "stationurl",
|
|
ItemName: "Rock FM",
|
|
Location: "tunein:station:s12345",
|
|
SourceAccount: "tunein_account",
|
|
IsPresetable: true,
|
|
},
|
|
},
|
|
detailed: true,
|
|
expected: []string{
|
|
"📻 Rock FM",
|
|
"Source: TuneIn Radio",
|
|
"ID: station123",
|
|
"Can be saved as preset",
|
|
"Location: tunein:station:s12345",
|
|
"Classification: Streaming",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Capture stdout
|
|
oldStdout := os.Stdout
|
|
r, w, _ := os.Pipe()
|
|
os.Stdout = w
|
|
|
|
// Call the function
|
|
printRecentItem(1, tt.item, tt.detailed)
|
|
|
|
// Restore stdout and read output
|
|
w.Close()
|
|
|
|
os.Stdout = oldStdout
|
|
|
|
var buf bytes.Buffer
|
|
|
|
_, err := buf.ReadFrom(r)
|
|
if err != nil {
|
|
t.Fatalf("failed to read output: %v", err)
|
|
}
|
|
|
|
output := buf.String()
|
|
|
|
// Check expected strings are present
|
|
for _, expected := range tt.expected {
|
|
if !bytes.Contains(buf.Bytes(), []byte(expected)) {
|
|
t.Errorf("expected output to contain %q, got:\n%s", expected, output)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetContentTypeIcon(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
item *models.RecentsResponseItem
|
|
expected string
|
|
}{
|
|
{
|
|
name: "track item",
|
|
item: &models.RecentsResponseItem{
|
|
ContentItem: &models.ContentItem{Type: "track"},
|
|
},
|
|
expected: "🎵",
|
|
},
|
|
{
|
|
name: "station item",
|
|
item: &models.RecentsResponseItem{
|
|
ContentItem: &models.ContentItem{Type: "stationurl"},
|
|
},
|
|
expected: "📻",
|
|
},
|
|
{
|
|
name: "playlist item",
|
|
item: &models.RecentsResponseItem{
|
|
ContentItem: &models.ContentItem{Type: "playlist"},
|
|
},
|
|
expected: "📋",
|
|
},
|
|
{
|
|
name: "album item",
|
|
item: &models.RecentsResponseItem{
|
|
ContentItem: &models.ContentItem{Type: "album"},
|
|
},
|
|
expected: "💿",
|
|
},
|
|
{
|
|
name: "container item",
|
|
item: &models.RecentsResponseItem{
|
|
ContentItem: &models.ContentItem{Type: "container"},
|
|
},
|
|
expected: "📁",
|
|
},
|
|
{
|
|
name: "unknown type",
|
|
item: &models.RecentsResponseItem{
|
|
ContentItem: &models.ContentItem{Type: "unknown"},
|
|
},
|
|
expected: "🎶",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := getContentTypeIcon(tt.item)
|
|
if result != tt.expected {
|
|
t.Errorf("expected %q, got %q", tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFormatSourceForDisplay(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
source string
|
|
expected string
|
|
}{
|
|
{"Spotify", "SPOTIFY", "Spotify"},
|
|
{"Local Music", "LOCAL_MUSIC", "Local Music"},
|
|
{"Stored Music", "STORED_MUSIC", "Stored Music"},
|
|
{"TuneIn", "TUNEIN", "TuneIn Radio"},
|
|
{"Pandora", "PANDORA", "Pandora"},
|
|
{"Amazon", "AMAZON", "Amazon Music"},
|
|
{"Deezer", "DEEZER", "Deezer"},
|
|
{"iHeart", "IHEART", "iHeartRadio"},
|
|
{"Bluetooth", "BLUETOOTH", "Bluetooth"},
|
|
{"AUX", "AUX", "AUX Input"},
|
|
{"AirPlay", "AIRPLAY", "AirPlay"},
|
|
{"Unknown", "UNKNOWN_SOURCE", "UNKNOWN_SOURCE"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := formatSourceForDisplay(tt.source)
|
|
if result != tt.expected {
|
|
t.Errorf("expected %q, got %q", tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTruncateString(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
maxLength int
|
|
expected string
|
|
}{
|
|
{
|
|
name: "short string",
|
|
input: "hello",
|
|
maxLength: 10,
|
|
expected: "hello",
|
|
},
|
|
{
|
|
name: "exact length",
|
|
input: "hello",
|
|
maxLength: 5,
|
|
expected: "hello",
|
|
},
|
|
{
|
|
name: "long string",
|
|
input: "this is a very long string that needs truncation",
|
|
maxLength: 20,
|
|
expected: "this is a very lo...",
|
|
},
|
|
{
|
|
name: "very short max length",
|
|
input: "hello world",
|
|
maxLength: 3,
|
|
expected: "...",
|
|
},
|
|
{
|
|
name: "zero length",
|
|
input: "hello",
|
|
maxLength: 0,
|
|
expected: "...",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := truncateString(tt.input, tt.maxLength)
|
|
if result != tt.expected {
|
|
t.Errorf("expected %q, got %q", tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test helper functions that would be used in full integration tests
|
|
func createTestRecentsResponse() *models.RecentsResponse {
|
|
return &models.RecentsResponse{
|
|
Items: []models.RecentsResponseItem{
|
|
{
|
|
DeviceID: "1004567890AA",
|
|
UTCTime: 1701300000,
|
|
ID: "spotify1",
|
|
ContentItem: &models.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",
|
|
},
|
|
},
|
|
{
|
|
DeviceID: "1004567890AA",
|
|
UTCTime: 1701200000,
|
|
ID: "local1",
|
|
ContentItem: &models.ContentItem{
|
|
Source: "LOCAL_MUSIC",
|
|
Type: "track",
|
|
Location: "/music/local_song.mp3",
|
|
IsPresetable: false,
|
|
ItemName: "Local Song - Local Artist",
|
|
},
|
|
},
|
|
{
|
|
DeviceID: "1004567890AA",
|
|
UTCTime: 1701100000,
|
|
ID: "tunein1",
|
|
ContentItem: &models.ContentItem{
|
|
Source: "TUNEIN",
|
|
Type: "stationurl",
|
|
Location: "tunein:station:s24939",
|
|
SourceAccount: "tunein",
|
|
IsPresetable: true,
|
|
ItemName: "BBC Radio 1",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestCreateTestRecentsResponse(t *testing.T) {
|
|
response := createTestRecentsResponse()
|
|
|
|
if response == nil {
|
|
t.Fatal("expected response, got nil")
|
|
}
|
|
|
|
if response.GetItemCount() != 3 {
|
|
t.Errorf("expected 3 items, got %d", response.GetItemCount())
|
|
}
|
|
|
|
if response.IsEmpty() {
|
|
t.Error("expected response not to be empty")
|
|
}
|
|
|
|
// Test filtering
|
|
spotifyItems := response.GetSpotifyItems()
|
|
if len(spotifyItems) != 1 {
|
|
t.Errorf("expected 1 Spotify item, got %d", len(spotifyItems))
|
|
}
|
|
|
|
localItems := response.GetLocalMusicItems()
|
|
if len(localItems) != 1 {
|
|
t.Errorf("expected 1 local music item, got %d", len(localItems))
|
|
}
|
|
|
|
tuneInItems := response.GetTuneInItems()
|
|
if len(tuneInItems) != 1 {
|
|
t.Errorf("expected 1 TuneIn item, got %d", len(tuneInItems))
|
|
}
|
|
|
|
tracks := response.GetTracks()
|
|
if len(tracks) != 2 {
|
|
t.Errorf("expected 2 tracks, got %d", len(tracks))
|
|
}
|
|
|
|
stations := response.GetStations()
|
|
if len(stations) != 1 {
|
|
t.Errorf("expected 1 station, got %d", len(stations))
|
|
}
|
|
|
|
presetableItems := response.GetPresetableItems()
|
|
if len(presetableItems) != 2 {
|
|
t.Errorf("expected 2 presetable items, got %d", len(presetableItems))
|
|
}
|
|
}
|