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) } }) } }