515 lines
14 KiB
Go
515 lines
14 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/models"
|
|
"gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/utils"
|
|
)
|
|
|
|
func TestClient_GetTracks(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
trackIDs []string
|
|
mockResponse []byte
|
|
mockError error
|
|
expectError bool
|
|
expectedTracks int
|
|
checkTrack func(*testing.T, *models.Track)
|
|
}{
|
|
{
|
|
name: "single track",
|
|
trackIDs: []string{"12345"},
|
|
mockResponse: []byte(`{
|
|
"result": [
|
|
{
|
|
"id": "12345",
|
|
"title": "Test Track",
|
|
"duration_ms": 180000,
|
|
"storage_id": "67890",
|
|
"cover_uri": "https://example.com/cover.jpg",
|
|
"genre": "rock",
|
|
"version": "album",
|
|
"explicit": false,
|
|
"available": true,
|
|
"available_for_premium_users": true,
|
|
"r128": -12.5,
|
|
"track_sharing_flag": "allowed",
|
|
"artists": [{"id": "1", "name": "Artist 1", "cover_uri": "https://example.com/artist.jpg"}],
|
|
"albums": [{"id": "789", "title": "Test Album", "cover_uri": "https://example.com/album.jpg", "year": 2023}],
|
|
"major": {"id": "major1", "name": "Major Label", "description": "Big label"},
|
|
"lyrics": {"id": "lyr1", "text": "Test lyrics", "available": true, "has_text": true}
|
|
}
|
|
]
|
|
}`),
|
|
expectedTracks: 1,
|
|
checkTrack: func(t *testing.T, tr *models.Track) {
|
|
if tr.ID != "12345" {
|
|
t.Errorf("Expected ID '12345', got '%s'", tr.ID)
|
|
}
|
|
if tr.Title != "Test Track" {
|
|
t.Errorf("Expected Title 'Test Track', got '%s'", tr.Title)
|
|
}
|
|
if tr.DurationMs != 180000 {
|
|
t.Errorf("Expected DurationMs 180000, got %d", tr.DurationMs)
|
|
}
|
|
if len(tr.Artists) != 1 {
|
|
t.Errorf("Expected 1 artist, got %d", len(tr.Artists))
|
|
}
|
|
if tr.Major == nil || tr.Major.Name != "Major Label" {
|
|
t.Errorf("Expected Major 'Major Label'")
|
|
}
|
|
if tr.Lyrics == nil || tr.Lyrics.Text != "Test lyrics" {
|
|
t.Errorf("Expected Lyrics 'Test lyrics'")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "multiple tracks",
|
|
trackIDs: []string{"12345", "67890"},
|
|
mockResponse: []byte(`{
|
|
"result": [
|
|
{"id": "12345", "title": "Track 1", "duration_ms": 100000, "storage_id": "1", "cover_uri": "", "genre": "", "version": "", "explicit": false, "available": true, "available_for_premium_users": true, "r128": 0, "track_sharing_flag": "", "artists": [], "albums": []},
|
|
{"id": "67890", "title": "Track 2", "duration_ms": 200000, "storage_id": "2", "cover_uri": "", "genre": "", "version": "", "explicit": false, "available": true, "available_for_premium_users": true, "r128": 0, "track_sharing_flag": "", "artists": [], "albums": []}
|
|
]
|
|
}`),
|
|
expectedTracks: 2,
|
|
checkTrack: func(t *testing.T, tr *models.Track) {
|
|
if tr.ID != "12345" && tr.ID != "67890" {
|
|
t.Errorf("Unexpected track ID: %s", tr.ID)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "empty track IDs",
|
|
trackIDs: []string{},
|
|
mockResponse: nil,
|
|
expectedTracks: 0,
|
|
},
|
|
{
|
|
name: "nil track IDs",
|
|
trackIDs: nil,
|
|
mockResponse: nil,
|
|
expectedTracks: 0,
|
|
},
|
|
{
|
|
name: "API error",
|
|
trackIDs: []string{"12345"},
|
|
mockError: utils.NewYandexMusicError("not found", utils.ErrNotFound),
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "empty result",
|
|
trackIDs: []string{"nonexistent"},
|
|
mockResponse: []byte(`{"result": []}`),
|
|
expectedTracks: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var server *httptest.Server
|
|
if tt.mockError != nil || tt.mockResponse != nil {
|
|
server = testServer(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/tracks" {
|
|
t.Errorf("Expected path '/tracks', got '%s'", r.URL.Path)
|
|
}
|
|
if tt.mockError != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{"error": "not found"}`))
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write(tt.mockResponse)
|
|
})
|
|
defer server.Close()
|
|
}
|
|
|
|
var client *Client
|
|
if server != nil {
|
|
client = newTestClient(server.URL)
|
|
} else {
|
|
client = newTestClient("http://localhost:9999")
|
|
}
|
|
|
|
tracks, err := client.GetTracks(context.Background(), tt.trackIDs)
|
|
|
|
if tt.expectError {
|
|
if err == nil {
|
|
t.Error("Expected error, got nil")
|
|
}
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if len(tracks) != tt.expectedTracks {
|
|
t.Errorf("Expected %d tracks, got %d", tt.expectedTracks, len(tracks))
|
|
return
|
|
}
|
|
|
|
if tt.checkTrack != nil {
|
|
for _, track := range tracks {
|
|
tt.checkTrack(t, track)
|
|
}
|
|
}
|
|
|
|
// Verify client is set on tracks
|
|
for _, track := range tracks {
|
|
if track.GetClient() == nil {
|
|
t.Error("Client not set on track")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_GetTrack(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
trackID string
|
|
mockResponse []byte
|
|
mockError error
|
|
expectError bool
|
|
checkTrack func(*testing.T, *models.Track)
|
|
}{
|
|
{
|
|
name: "success",
|
|
trackID: "12345",
|
|
mockResponse: []byte(`{
|
|
"result": [
|
|
{
|
|
"id": "12345",
|
|
"title": "Test Track",
|
|
"duration_ms": 180000,
|
|
"storage_id": "67890",
|
|
"cover_uri": "https://example.com/cover.jpg",
|
|
"genre": "rock",
|
|
"version": "album",
|
|
"explicit": false,
|
|
"available": true,
|
|
"available_for_premium_users": true,
|
|
"r128": -12.5,
|
|
"track_sharing_flag": "allowed",
|
|
"artists": [{"id": "1", "name": "Artist 1", "cover_uri": "https://example.com/artist.jpg"}],
|
|
"albums": [{"id": "789", "title": "Test Album", "cover_uri": "https://example.com/album.jpg", "year": 2023}],
|
|
"major": {"id": "major1", "name": "Major Label", "description": "Big label"},
|
|
"lyrics": {"id": "lyr1", "text": "Test lyrics", "available": true, "has_text": true}
|
|
}
|
|
]
|
|
}`),
|
|
checkTrack: func(t *testing.T, tr *models.Track) {
|
|
if tr.ID != "12345" {
|
|
t.Errorf("Expected ID '12345', got '%s'", tr.ID)
|
|
}
|
|
if tr.Title != "Test Track" {
|
|
t.Errorf("Expected Title 'Test Track', got '%s'", tr.Title)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "not found",
|
|
trackID: "nonexistent",
|
|
mockResponse: []byte(`{"result": []}`),
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "API error",
|
|
trackID: "12345",
|
|
mockError: utils.NewYandexMusicError("unauthorized", utils.ErrUnauthorized),
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var server *httptest.Server
|
|
if tt.mockError != nil || tt.mockResponse != nil {
|
|
server = testServer(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/tracks" {
|
|
t.Errorf("Expected path '/tracks', got '%s'", r.URL.Path)
|
|
}
|
|
if r.URL.Query().Get("track-ids") != tt.trackID {
|
|
t.Errorf("Expected track-ids '%s', got '%s'", tt.trackID, r.URL.Query().Get("track-ids"))
|
|
}
|
|
if tt.mockError != nil {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{"error": "unauthorized"}`))
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write(tt.mockResponse)
|
|
})
|
|
defer server.Close()
|
|
}
|
|
|
|
var client *Client
|
|
if server != nil {
|
|
client = newTestClient(server.URL)
|
|
} else {
|
|
client = newTestClient("http://localhost:9999")
|
|
}
|
|
|
|
track, err := client.GetTrack(context.Background(), tt.trackID)
|
|
|
|
if tt.expectError {
|
|
if err == nil {
|
|
t.Error("Expected error, got nil")
|
|
}
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
if track == nil {
|
|
t.Fatal("Expected track, got nil")
|
|
}
|
|
|
|
if tt.checkTrack != nil {
|
|
tt.checkTrack(t, track)
|
|
}
|
|
|
|
if track.GetClient() == nil {
|
|
t.Error("Client not set on track")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_SearchTracks(t *testing.T) {
|
|
// Create test server
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/search" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
|
|
query := r.URL.Query()
|
|
if query.Get("type") != "track" {
|
|
t.Errorf("Expected type=track, got %s", query.Get("type"))
|
|
}
|
|
|
|
page := query.Get("page")
|
|
pageSize := query.Get("page_size")
|
|
|
|
response := map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"tracks": map[string]interface{}{
|
|
"total": 2,
|
|
"results": []map[string]interface{}{
|
|
{
|
|
"id": "1",
|
|
"title": "Track 1",
|
|
"duration_ms": 100000,
|
|
"storage_id": "1",
|
|
"cover_uri": "",
|
|
"genre": "",
|
|
"version": "",
|
|
"explicit": false,
|
|
"available": true,
|
|
"available_for_premium_users": true,
|
|
"r128": 0,
|
|
"track_sharing_flag": "",
|
|
"artists": []interface{}{},
|
|
"albums": []interface{}{},
|
|
},
|
|
{
|
|
"id": "2",
|
|
"title": "Track 2",
|
|
"duration_ms": 200000,
|
|
"storage_id": "2",
|
|
"cover_uri": "",
|
|
"genre": "",
|
|
"version": "",
|
|
"explicit": false,
|
|
"available": true,
|
|
"available_for_premium_users": true,
|
|
"r128": 0,
|
|
"track_sharing_flag": "",
|
|
"artists": []interface{}{},
|
|
"albums": []interface{}{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Verify pagination
|
|
if page != "1" || pageSize != "20" {
|
|
t.Errorf("Unexpected pagination: page=%s, page_size=%s", page, pageSize)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(response)
|
|
}))
|
|
defer server.Close()
|
|
|
|
// Create client with test server URL
|
|
config := ClientConfig{
|
|
BaseURL: server.URL,
|
|
Token: "test-token",
|
|
}
|
|
client := NewClient(config)
|
|
|
|
ctx := context.Background()
|
|
result, err := client.SearchTracks(ctx, "test query", 1, 20)
|
|
if err != nil {
|
|
t.Fatalf("SearchTracks failed: %v", err)
|
|
}
|
|
|
|
if result == nil {
|
|
t.Fatal("Expected search result, got nil")
|
|
}
|
|
|
|
if result.Total != 2 {
|
|
t.Errorf("Expected total 2, got %d", result.Total)
|
|
}
|
|
|
|
if len(result.Results) != 2 {
|
|
t.Errorf("Expected 2 results, got %d", len(result.Results))
|
|
}
|
|
|
|
for i, track := range result.Results {
|
|
if track.GetClient() == nil {
|
|
t.Errorf("Track %d: Client not set", i)
|
|
}
|
|
if track.ID != "1" && track.ID != "2" {
|
|
t.Errorf("Track %d: Unexpected ID %s", i, track.ID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestClient_SearchTracks_Mocked(t *testing.T) {
|
|
server := testServer(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet || r.URL.Path != "/search" {
|
|
t.Errorf("Unexpected request: %s %s", r.Method, r.URL.Path)
|
|
return
|
|
}
|
|
if r.URL.Query().Get("type") != "track" {
|
|
t.Errorf("Expected type=track, got %s", r.URL.Query().Get("type"))
|
|
}
|
|
|
|
mockSearchResult := map[string]interface{}{
|
|
"result": map[string]interface{}{
|
|
"tracks": map[string]interface{}{
|
|
"total": 2,
|
|
"results": []map[string]interface{}{
|
|
{
|
|
"id": "1",
|
|
"title": "Track 1",
|
|
"duration_ms": 100000,
|
|
"storage_id": "1",
|
|
"cover_uri": "",
|
|
"genre": "",
|
|
"version": "",
|
|
"explicit": false,
|
|
"available": true,
|
|
"available_for_premium_users": true,
|
|
"r128": 0,
|
|
"track_sharing_flag": "",
|
|
"artists": []interface{}{},
|
|
"albums": []interface{}{},
|
|
},
|
|
{
|
|
"id": "2",
|
|
"title": "Track 2",
|
|
"duration_ms": 200000,
|
|
"storage_id": "2",
|
|
"cover_uri": "",
|
|
"genre": "",
|
|
"version": "",
|
|
"explicit": false,
|
|
"available": true,
|
|
"available_for_premium_users": true,
|
|
"r128": 0,
|
|
"track_sharing_flag": "",
|
|
"artists": []interface{}{},
|
|
"albums": []interface{}{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(mockSearchResult)
|
|
})
|
|
defer server.Close()
|
|
|
|
client := newTestClient(server.URL)
|
|
|
|
ctx := context.Background()
|
|
result, err := client.SearchTracks(ctx, "test query", 1, 20)
|
|
|
|
if err != nil {
|
|
t.Fatalf("SearchTracks failed: %v", err)
|
|
}
|
|
|
|
if result == nil {
|
|
t.Fatal("Expected search result, got nil")
|
|
}
|
|
|
|
if result.Total != 2 {
|
|
t.Errorf("Expected total 2, got %d", result.Total)
|
|
}
|
|
|
|
if len(result.Results) != 2 {
|
|
t.Errorf("Expected 2 results, got %d", len(result.Results))
|
|
}
|
|
|
|
for i, track := range result.Results {
|
|
if track.GetClient() == nil {
|
|
t.Errorf("Track %d: Client not set", i)
|
|
}
|
|
}
|
|
|
|
// Test error case
|
|
errorServer := testServer(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{"error": "network error"}`))
|
|
})
|
|
defer errorServer.Close()
|
|
|
|
errorClient := newTestClient(errorServer.URL)
|
|
_, err = errorClient.SearchTracks(ctx, "test", 1, 20)
|
|
if err == nil {
|
|
t.Error("Expected error for network failure")
|
|
}
|
|
if !utils.IsNetworkError(err) {
|
|
t.Errorf("Expected network error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestJoinIDs(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input []string
|
|
expected string
|
|
}{
|
|
{"empty", []string{}, ""},
|
|
{"single", []string{"123"}, "123"},
|
|
{"multiple", []string{"123", "456", "789"}, "123,456,789"},
|
|
{"with empty", []string{"123", "", "456"}, "123,,456"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := joinIDs(tt.input)
|
|
if result != tt.expected {
|
|
t.Errorf("joinIDs(%v) = %q, want %q", tt.input, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|