mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 16:46:17 +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.
719 lines
19 KiB
Go
719 lines
19 KiB
Go
package client
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gesellix/bose-soundtouch/pkg/models"
|
|
)
|
|
|
|
func TestClient_SetMusicServiceAccount(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
credentials *models.MusicServiceCredentials
|
|
serverStatus int
|
|
serverBody string
|
|
wantError bool
|
|
errorMessage string
|
|
}{
|
|
{
|
|
name: "Valid Spotify credentials",
|
|
credentials: models.NewSpotifyCredentials("user@spotify.com", "mypassword"),
|
|
serverStatus: http.StatusOK,
|
|
serverBody: `<?xml version="1.0" encoding="UTF-8" ?><status>/setMusicServiceAccount</status>`,
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "Valid Pandora credentials",
|
|
credentials: models.NewPandoraCredentials("pandora_user", "pandora_pass"),
|
|
serverStatus: http.StatusOK,
|
|
serverBody: `<?xml version="1.0" encoding="UTF-8" ?><status>/setMusicServiceAccount</status>`,
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "Valid STORED_MUSIC credentials",
|
|
credentials: models.NewStoredMusicCredentials("d09708a1-5953-44bc-a413-123456789012/0", "My NAS Library"),
|
|
serverStatus: http.StatusOK,
|
|
serverBody: `<?xml version="1.0" encoding="UTF-8" ?><status>/setMusicServiceAccount</status>`,
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "Nil credentials",
|
|
credentials: nil,
|
|
wantError: true,
|
|
errorMessage: "credentials cannot be nil",
|
|
},
|
|
{
|
|
name: "Invalid credentials - empty source",
|
|
credentials: &models.MusicServiceCredentials{
|
|
Source: "",
|
|
DisplayName: "Test Service",
|
|
User: "testuser",
|
|
Pass: "testpass",
|
|
},
|
|
wantError: true,
|
|
errorMessage: "invalid credentials: source cannot be empty",
|
|
},
|
|
{
|
|
name: "Invalid credentials - empty user",
|
|
credentials: &models.MusicServiceCredentials{
|
|
Source: "SPOTIFY",
|
|
DisplayName: "Spotify",
|
|
User: "",
|
|
Pass: "testpass",
|
|
},
|
|
wantError: true,
|
|
errorMessage: "invalid credentials: user cannot be empty",
|
|
},
|
|
{
|
|
name: "Invalid credentials - empty password for non-STORED_MUSIC",
|
|
credentials: &models.MusicServiceCredentials{
|
|
Source: "SPOTIFY",
|
|
DisplayName: "Spotify",
|
|
User: "testuser",
|
|
Pass: "",
|
|
},
|
|
wantError: true,
|
|
errorMessage: "invalid credentials: password cannot be empty for SPOTIFY",
|
|
},
|
|
{
|
|
name: "Server error",
|
|
credentials: models.NewSpotifyCredentials("user@spotify.com", "mypassword"),
|
|
serverStatus: http.StatusInternalServerError,
|
|
serverBody: "Internal Server Error",
|
|
wantError: true,
|
|
errorMessage: "failed to set music service account for SPOTIFY",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var receivedRequest *models.MusicServiceCredentials
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/setMusicServiceAccount" {
|
|
t.Errorf("Expected path /setMusicServiceAccount, got %s", r.URL.Path)
|
|
}
|
|
|
|
if r.Method != "POST" {
|
|
t.Errorf("Expected POST method, got %s", r.Method)
|
|
}
|
|
|
|
// Parse request body to verify credentials
|
|
if tt.credentials != nil {
|
|
var req models.MusicServiceCredentials
|
|
if err := xml.NewDecoder(r.Body).Decode(&req); err == nil {
|
|
receivedRequest = &req
|
|
}
|
|
}
|
|
|
|
w.WriteHeader(tt.serverStatus)
|
|
|
|
if tt.serverBody != "" {
|
|
_, _ = w.Write([]byte(tt.serverBody))
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
config := &Config{
|
|
Host: server.URL[7:],
|
|
Port: 80,
|
|
Timeout: testTimeout,
|
|
}
|
|
client := NewClient(config)
|
|
client.baseURL = server.URL
|
|
|
|
err := client.SetMusicServiceAccount(tt.credentials)
|
|
|
|
if tt.wantError {
|
|
if err == nil {
|
|
t.Error("Expected error but got none")
|
|
} else if tt.errorMessage != "" && !strings.Contains(err.Error(), tt.errorMessage) {
|
|
t.Errorf("Expected error message to contain %q, got %q", tt.errorMessage, err.Error())
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Verify request was sent correctly
|
|
if receivedRequest != nil {
|
|
if receivedRequest.Source != tt.credentials.Source {
|
|
t.Errorf("Expected source %s, got %s", tt.credentials.Source, receivedRequest.Source)
|
|
}
|
|
|
|
if receivedRequest.User != tt.credentials.User {
|
|
t.Errorf("Expected user %s, got %s", tt.credentials.User, receivedRequest.User)
|
|
}
|
|
|
|
if receivedRequest.Pass != tt.credentials.Pass {
|
|
t.Errorf("Expected pass %s, got %s", tt.credentials.Pass, receivedRequest.Pass)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_RemoveMusicServiceAccount(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
credentials *models.MusicServiceCredentials
|
|
serverStatus int
|
|
serverBody string
|
|
wantError bool
|
|
errorMessage string
|
|
}{
|
|
{
|
|
name: "Valid Spotify removal",
|
|
credentials: models.NewSpotifyCredentials("user@spotify.com", "mypassword"),
|
|
serverStatus: http.StatusOK,
|
|
serverBody: `<?xml version="1.0" encoding="UTF-8" ?><status>/removeMusicServiceAccount</status>`,
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "Valid Pandora removal",
|
|
credentials: models.NewPandoraCredentials("pandora_user", "pandora_pass"),
|
|
serverStatus: http.StatusOK,
|
|
serverBody: `<?xml version="1.0" encoding="UTF-8" ?><status>/removeMusicServiceAccount</status>`,
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "Nil credentials",
|
|
credentials: nil,
|
|
wantError: true,
|
|
errorMessage: "credentials cannot be nil",
|
|
},
|
|
{
|
|
name: "Empty source",
|
|
credentials: &models.MusicServiceCredentials{
|
|
Source: "",
|
|
User: "testuser",
|
|
},
|
|
wantError: true,
|
|
errorMessage: "source cannot be empty",
|
|
},
|
|
{
|
|
name: "Empty user",
|
|
credentials: &models.MusicServiceCredentials{
|
|
Source: "SPOTIFY",
|
|
User: "",
|
|
},
|
|
wantError: true,
|
|
errorMessage: "user cannot be empty",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var receivedRequest *models.MusicServiceCredentials
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/removeMusicServiceAccount" {
|
|
t.Errorf("Expected path /removeMusicServiceAccount, got %s", r.URL.Path)
|
|
}
|
|
|
|
// Parse request body to verify credentials have empty password
|
|
if tt.credentials != nil {
|
|
var req models.MusicServiceCredentials
|
|
if err := xml.NewDecoder(r.Body).Decode(&req); err == nil {
|
|
receivedRequest = &req
|
|
}
|
|
}
|
|
|
|
w.WriteHeader(tt.serverStatus)
|
|
|
|
if tt.serverBody != "" {
|
|
_, _ = w.Write([]byte(tt.serverBody))
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
config := &Config{
|
|
Host: server.URL[7:],
|
|
Port: 80,
|
|
Timeout: testTimeout,
|
|
}
|
|
client := NewClient(config)
|
|
client.baseURL = server.URL
|
|
|
|
err := client.RemoveMusicServiceAccount(tt.credentials)
|
|
|
|
if tt.wantError {
|
|
if err == nil {
|
|
t.Error("Expected error but got none")
|
|
} else if tt.errorMessage != "" && !strings.Contains(err.Error(), tt.errorMessage) {
|
|
t.Errorf("Expected error message to contain %q, got %q", tt.errorMessage, err.Error())
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Verify password was cleared for removal
|
|
if receivedRequest != nil && receivedRequest.Pass != "" {
|
|
t.Errorf("Expected empty password for removal, got %s", receivedRequest.Pass)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_AddSpotifyAccount(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/setMusicServiceAccount" {
|
|
t.Errorf("Expected path /setMusicServiceAccount, got %s", r.URL.Path)
|
|
}
|
|
|
|
var req models.MusicServiceCredentials
|
|
if err := xml.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
t.Errorf("Failed to decode request: %v", err)
|
|
}
|
|
|
|
if req.Source != "SPOTIFY" {
|
|
t.Errorf("Expected source SPOTIFY, got %s", req.Source)
|
|
}
|
|
|
|
if req.User != "test@spotify.com" {
|
|
t.Errorf("Expected user test@spotify.com, got %s", req.User)
|
|
}
|
|
|
|
if req.Pass != "mypassword" {
|
|
t.Errorf("Expected password mypassword, got %s", req.Pass)
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8" ?><status>/setMusicServiceAccount</status>`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
config := &Config{
|
|
Host: server.URL[7:],
|
|
Port: 80,
|
|
Timeout: testTimeout,
|
|
}
|
|
client := NewClient(config)
|
|
client.baseURL = server.URL
|
|
|
|
err := client.AddSpotifyAccount("test@spotify.com", "mypassword")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClient_RemoveSpotifyAccount(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/removeMusicServiceAccount" {
|
|
t.Errorf("Expected path /removeMusicServiceAccount, got %s", r.URL.Path)
|
|
}
|
|
|
|
var req models.MusicServiceCredentials
|
|
if err := xml.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
t.Errorf("Failed to decode request: %v", err)
|
|
}
|
|
|
|
if req.Source != "SPOTIFY" {
|
|
t.Errorf("Expected source SPOTIFY, got %s", req.Source)
|
|
}
|
|
|
|
if req.User != "test@spotify.com" {
|
|
t.Errorf("Expected user test@spotify.com, got %s", req.User)
|
|
}
|
|
|
|
if req.Pass != "" {
|
|
t.Errorf("Expected empty password for removal, got %s", req.Pass)
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8" ?><status>/removeMusicServiceAccount</status>`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
config := &Config{
|
|
Host: server.URL[7:],
|
|
Port: 80,
|
|
Timeout: testTimeout,
|
|
}
|
|
client := NewClient(config)
|
|
client.baseURL = server.URL
|
|
|
|
err := client.RemoveSpotifyAccount("test@spotify.com")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClient_AddStoredMusicAccount(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
var req models.MusicServiceCredentials
|
|
if err := xml.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
t.Errorf("Failed to decode request: %v", err)
|
|
}
|
|
|
|
if req.Source != "STORED_MUSIC" {
|
|
t.Errorf("Expected source STORED_MUSIC, got %s", req.Source)
|
|
}
|
|
|
|
if req.User != "d09708a1-5953-44bc-a413-123456789012/0" {
|
|
t.Errorf("Expected NAS user ID, got %s", req.User)
|
|
}
|
|
|
|
if req.DisplayName != "My NAS Library" {
|
|
t.Errorf("Expected display name 'My NAS Library', got %s", req.DisplayName)
|
|
}
|
|
|
|
// STORED_MUSIC should have empty password
|
|
if req.Pass != "" {
|
|
t.Errorf("Expected empty password for STORED_MUSIC, got %s", req.Pass)
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8" ?><status>/setMusicServiceAccount</status>`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
config := &Config{
|
|
Host: server.URL[7:],
|
|
Port: 80,
|
|
Timeout: testTimeout,
|
|
}
|
|
client := NewClient(config)
|
|
client.baseURL = server.URL
|
|
|
|
err := client.AddStoredMusicAccount("d09708a1-5953-44bc-a413-123456789012/0", "My NAS Library")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClient_AccountManagementErrors(t *testing.T) {
|
|
// Test network error
|
|
client := NewClient(&Config{
|
|
Host: "non-existent-host.invalid",
|
|
Port: 8090,
|
|
Timeout: 1 * time.Second,
|
|
})
|
|
|
|
credentials := models.NewSpotifyCredentials("user@spotify.com", "password")
|
|
|
|
err := client.SetMusicServiceAccount(credentials)
|
|
if err == nil {
|
|
t.Error("Expected error for network error")
|
|
}
|
|
|
|
err = client.RemoveMusicServiceAccount(credentials)
|
|
if err == nil {
|
|
t.Error("Expected error for network error")
|
|
}
|
|
}
|
|
|
|
// Test convenience methods for all supported services
|
|
func TestClient_AddAmazonMusicAccount(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/setMusicServiceAccount" {
|
|
t.Errorf("Expected path /setMusicServiceAccount, got %s", r.URL.Path)
|
|
}
|
|
|
|
var req models.MusicServiceCredentials
|
|
if err := xml.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
t.Errorf("Failed to decode request: %v", err)
|
|
}
|
|
|
|
if req.Source != "AMAZON" {
|
|
t.Errorf("Expected source AMAZON, got %s", req.Source)
|
|
}
|
|
|
|
if req.User != "test@amazon.com" {
|
|
t.Errorf("Expected user test@amazon.com, got %s", req.User)
|
|
}
|
|
|
|
if req.Pass != "mypassword" {
|
|
t.Errorf("Expected password mypassword, got %s", req.Pass)
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8" ?><status>/setMusicServiceAccount</status>`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
config := &Config{
|
|
Host: server.URL[7:],
|
|
Port: 80,
|
|
Timeout: testTimeout,
|
|
}
|
|
client := NewClient(config)
|
|
client.baseURL = server.URL
|
|
|
|
err := client.AddAmazonMusicAccount("test@amazon.com", "mypassword")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClient_RemoveAmazonMusicAccount(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/removeMusicServiceAccount" {
|
|
t.Errorf("Expected path /removeMusicServiceAccount, got %s", r.URL.Path)
|
|
}
|
|
|
|
var req models.MusicServiceCredentials
|
|
if err := xml.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
t.Errorf("Failed to decode request: %v", err)
|
|
}
|
|
|
|
if req.Source != "AMAZON" {
|
|
t.Errorf("Expected source AMAZON, got %s", req.Source)
|
|
}
|
|
|
|
if req.User != "test@amazon.com" {
|
|
t.Errorf("Expected user test@amazon.com, got %s", req.User)
|
|
}
|
|
|
|
if req.Pass != "" {
|
|
t.Errorf("Expected empty password for removal, got %s", req.Pass)
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8" ?><status>/removeMusicServiceAccount</status>`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
config := &Config{
|
|
Host: server.URL[7:],
|
|
Port: 80,
|
|
Timeout: testTimeout,
|
|
}
|
|
client := NewClient(config)
|
|
client.baseURL = server.URL
|
|
|
|
err := client.RemoveAmazonMusicAccount("test@amazon.com")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClient_AddDeezerAccount(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/setMusicServiceAccount" {
|
|
t.Errorf("Expected path /setMusicServiceAccount, got %s", r.URL.Path)
|
|
}
|
|
|
|
var req models.MusicServiceCredentials
|
|
if err := xml.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
t.Errorf("Failed to decode request: %v", err)
|
|
}
|
|
|
|
if req.Source != "DEEZER" {
|
|
t.Errorf("Expected source DEEZER, got %s", req.Source)
|
|
}
|
|
|
|
if req.User != "deezer_user" {
|
|
t.Errorf("Expected user deezer_user, got %s", req.User)
|
|
}
|
|
|
|
if req.Pass != "deezer_pass" {
|
|
t.Errorf("Expected password deezer_pass, got %s", req.Pass)
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8" ?><status>/setMusicServiceAccount</status>`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
config := &Config{
|
|
Host: server.URL[7:],
|
|
Port: 80,
|
|
Timeout: testTimeout,
|
|
}
|
|
client := NewClient(config)
|
|
client.baseURL = server.URL
|
|
|
|
err := client.AddDeezerAccount("deezer_user", "deezer_pass")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClient_RemoveDeezerAccount(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/removeMusicServiceAccount" {
|
|
t.Errorf("Expected path /removeMusicServiceAccount, got %s", r.URL.Path)
|
|
}
|
|
|
|
var req models.MusicServiceCredentials
|
|
if err := xml.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
t.Errorf("Failed to decode request: %v", err)
|
|
}
|
|
|
|
if req.Source != "DEEZER" {
|
|
t.Errorf("Expected source DEEZER, got %s", req.Source)
|
|
}
|
|
|
|
if req.User != "deezer_user" {
|
|
t.Errorf("Expected user deezer_user, got %s", req.User)
|
|
}
|
|
|
|
if req.Pass != "" {
|
|
t.Errorf("Expected empty password for removal, got %s", req.Pass)
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8" ?><status>/removeMusicServiceAccount</status>`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
config := &Config{
|
|
Host: server.URL[7:],
|
|
Port: 80,
|
|
Timeout: testTimeout,
|
|
}
|
|
client := NewClient(config)
|
|
client.baseURL = server.URL
|
|
|
|
err := client.RemoveDeezerAccount("deezer_user")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClient_AddIHeartRadioAccount(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/setMusicServiceAccount" {
|
|
t.Errorf("Expected path /setMusicServiceAccount, got %s", r.URL.Path)
|
|
}
|
|
|
|
var req models.MusicServiceCredentials
|
|
if err := xml.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
t.Errorf("Failed to decode request: %v", err)
|
|
}
|
|
|
|
if req.Source != "IHEART" {
|
|
t.Errorf("Expected source IHEART, got %s", req.Source)
|
|
}
|
|
|
|
if req.User != "iheart_user" {
|
|
t.Errorf("Expected user iheart_user, got %s", req.User)
|
|
}
|
|
|
|
if req.Pass != "iheart_pass" {
|
|
t.Errorf("Expected password iheart_pass, got %s", req.Pass)
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8" ?><status>/setMusicServiceAccount</status>`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
config := &Config{
|
|
Host: server.URL[7:],
|
|
Port: 80,
|
|
Timeout: testTimeout,
|
|
}
|
|
client := NewClient(config)
|
|
client.baseURL = server.URL
|
|
|
|
err := client.AddIHeartRadioAccount("iheart_user", "iheart_pass")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClient_RemoveIHeartRadioAccount(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/removeMusicServiceAccount" {
|
|
t.Errorf("Expected path /removeMusicServiceAccount, got %s", r.URL.Path)
|
|
}
|
|
|
|
var req models.MusicServiceCredentials
|
|
if err := xml.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
t.Errorf("Failed to decode request: %v", err)
|
|
}
|
|
|
|
if req.Source != "IHEART" {
|
|
t.Errorf("Expected source IHEART, got %s", req.Source)
|
|
}
|
|
|
|
if req.User != "iheart_user" {
|
|
t.Errorf("Expected user iheart_user, got %s", req.User)
|
|
}
|
|
|
|
if req.Pass != "" {
|
|
t.Errorf("Expected empty password for removal, got %s", req.Pass)
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8" ?><status>/removeMusicServiceAccount</status>`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
config := &Config{
|
|
Host: server.URL[7:],
|
|
Port: 80,
|
|
Timeout: testTimeout,
|
|
}
|
|
client := NewClient(config)
|
|
client.baseURL = server.URL
|
|
|
|
err := client.RemoveIHeartRadioAccount("iheart_user")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestClient_ConvenienceMethodsExist(_ *testing.T) {
|
|
client := NewClient(&Config{
|
|
Host: "localhost",
|
|
Port: 8090,
|
|
Timeout: testTimeout,
|
|
})
|
|
|
|
// Test that convenience methods exist (compilation test)
|
|
var err error
|
|
|
|
// Spotify
|
|
err = client.AddSpotifyAccount("user", "pass")
|
|
_ = err // Expect network error, but method should exist
|
|
|
|
err = client.RemoveSpotifyAccount("user")
|
|
_ = err
|
|
|
|
// Pandora
|
|
err = client.AddPandoraAccount("user", "pass")
|
|
_ = err
|
|
|
|
err = client.RemovePandoraAccount("user")
|
|
_ = err
|
|
|
|
// Amazon Music
|
|
err = client.AddAmazonMusicAccount("user", "pass")
|
|
_ = err
|
|
|
|
err = client.RemoveAmazonMusicAccount("user")
|
|
_ = err
|
|
|
|
// Deezer
|
|
err = client.AddDeezerAccount("user", "pass")
|
|
_ = err
|
|
|
|
err = client.RemoveDeezerAccount("user")
|
|
_ = err
|
|
|
|
// iHeartRadio
|
|
err = client.AddIHeartRadioAccount("user", "pass")
|
|
_ = err
|
|
|
|
err = client.RemoveIHeartRadioAccount("user")
|
|
_ = err
|
|
|
|
// STORED_MUSIC
|
|
err = client.AddStoredMusicAccount("guid/0", "Display Name")
|
|
_ = err
|
|
|
|
err = client.RemoveStoredMusicAccount("guid/0", "Display Name")
|
|
_ = err
|
|
}
|