Initial commit
This commit is contained in:
commit
02a6dabcfb
62 files changed
+15751
No files matched your search
@@ -0,0 +1,514 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user