package client import ( "context" "encoding/json" "net/http" "testing" "gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/utils" ) // playlistResponse creates a JSON response for a playlist list func playlistResponse(playlists []map[string]any) []byte { resp := map[string]any{ "result": playlists, } data, _ := json.Marshal(resp) return data } // singlePlaylistResponse creates a JSON response for a single playlist func singlePlaylistResponse(playlist map[string]any) []byte { resp := map[string]any{ "result": playlist, } data, _ := json.Marshal(resp) return data } // errorResponse creates an error JSON response func errorResponse(errMsg string) []byte { resp := map[string]any{ "error": errMsg, } data, _ := json.Marshal(resp) return data } func TestClient_GetPlaylists(t *testing.T) { // Sample playlist data playlists := []map[string]any{ { "kind": 1, "title": "Playlist 1", "description": "First playlist", "cover_uri": "https://example.com/cover1.jpg", "created": "2023-01-01T00:00:00+03:00", "modified": "2023-01-02T00:00:00+03:00", "track_count": 10, "duration_ms": 1800000, "likes_count": 5, "is_public": true, "revision": 1, "uuid": "uuid-1", "tags": []string{"rock", "pop"}, "access_type": "full", "available": true, "content_warning": "", "genre": "rock", "owner_uid": 12345, "shared": false, "snapshot_id": "snap-1", "owner": map[string]any{ "uid": 12345, "login": "user1", "display_name": "User One", "cover_uri": "https://example.com/user1.jpg", "sex": "m", "public_playlists": 10, "followers_count": 100, "followings_count": 50, "has_playlists": true, "available": true, }, "tracks": []any{}, "similar_playlists": []any{}, }, { "kind": 2, "title": "Playlist 2", "description": "Second playlist", "cover_uri": "https://example.com/cover2.jpg", "created": "2023-02-01T00:00:00+03:00", "modified": "2023-02-02T00:00:00+03:00", "track_count": 5, "duration_ms": 900000, "likes_count": 3, "is_public": false, "revision": 2, "uuid": "uuid-2", "tags": []string{"jazz"}, "access_type": "full", "available": true, "content_warning": "", "genre": "jazz", "owner_uid": 12345, "shared": false, "snapshot_id": "snap-2", "owner": map[string]any{ "uid": 12345, "login": "user1", "display_name": "User One", "cover_uri": "https://example.com/user1.jpg", "sex": "m", "public_playlists": 10, "followers_count": 100, "followings_count": 50, "has_playlists": true, "available": true, }, "tracks": []any{}, "similar_playlists": []any{}, }, } server := testServer(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/playlists" { t.Errorf("Expected path /playlists, got %s", r.URL.Path) } if r.URL.Query().Get("kinds") != "1,2" { t.Errorf("Expected kinds=1,2, got %s", r.URL.Query().Get("kinds")) } if r.Header.Get("Authorization") != "OAuth test-token" { t.Errorf("Expected Authorization header, got %s", r.Header.Get("Authorization")) } w.Header().Set("Content-Type", "application/json") w.Write(playlistResponse(playlists)) }) defer server.Close() client := newTestClient(server.URL) result, err := client.GetPlaylists(context.Background(), []int{1, 2}) if err != nil { t.Fatalf("GetPlaylists failed: %v", err) } if len(result) != 2 { t.Fatalf("Expected 2 playlists, got %d", len(result)) } if result[0].Kind != 1 { t.Errorf("Expected first playlist kind 1, got %d", result[0].Kind) } if result[0].Title != "Playlist 1" { t.Errorf("Expected first playlist title 'Playlist 1', got '%s'", result[0].Title) } if result[0].Description != "First playlist" { t.Errorf("Expected first playlist description 'First playlist', got '%s'", result[0].Description) } if result[0].TrackCount != 10 { t.Errorf("Expected first playlist track_count 10, got %d", result[0].TrackCount) } if result[0].Owner == nil || result[0].Owner.UID != 12345 { t.Errorf("Expected owner UID 12345") } if result[0].Owner.Login != "user1" { t.Errorf("Expected owner login 'user1', got '%s'", result[0].Owner.Login) } if result[1].Kind != 2 { t.Errorf("Expected second playlist kind 2, got %d", result[1].Kind) } if result[1].Title != "Playlist 2" { t.Errorf("Expected second playlist title 'Playlist 2', got '%s'", result[1].Title) } if result[1].IsPublic != false { t.Errorf("Expected second playlist is_public false, got %v", result[1].IsPublic) } // Verify client is set on playlists for _, p := range result { if p.GetClient() != client { t.Error("Expected client to be set on playlist") } } } func TestClient_GetPlaylists_Empty(t *testing.T) { server := testServer(func(w http.ResponseWriter, r *http.Request) { t.Error("Server should not be called for empty kinds") }) defer server.Close() client := newTestClient(server.URL) result, err := client.GetPlaylists(context.Background(), []int{}) if err != nil { t.Fatalf("GetPlaylists failed: %v", err) } if result != nil { t.Errorf("Expected nil for empty kinds, got %v", result) } } func TestClient_GetPlaylist(t *testing.T) { playlist := map[string]any{ "kind": 42, "title": "My Playlist", "description": "A test playlist", "cover_uri": "https://example.com/cover.jpg", "created": "2023-01-01T00:00:00+03:00", "modified": "2023-01-02T00:00:00+03:00", "track_count": 15, "duration_ms": 2700000, "likes_count": 10, "is_public": true, "revision": 5, "uuid": "uuid-42", "tags": []string{"rock", "metal"}, "access_type": "full", "available": true, "content_warning": "", "genre": "rock", "owner_uid": 999, "shared": false, "snapshot_id": "snap-42", "owner": map[string]any{ "uid": 999, "login": "testuser", "display_name": "Test User", "cover_uri": "https://example.com/user.jpg", "sex": "f", "public_playlists": 5, "followers_count": 200, "followings_count": 100, "has_playlists": true, "available": true, }, "tracks": []any{}, "similar_playlists": []any{}, } server := testServer(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/playlists" { t.Errorf("Expected path /playlists, got %s", r.URL.Path) } if r.URL.Query().Get("kinds") != "42" { t.Errorf("Expected kinds=42, got %s", r.URL.Query().Get("kinds")) } w.Header().Set("Content-Type", "application/json") w.Write(playlistResponse([]map[string]any{playlist})) }) defer server.Close() client := newTestClient(server.URL) result, err := client.GetPlaylist(context.Background(), 42) if err != nil { t.Fatalf("GetPlaylist failed: %v", err) } if result == nil { t.Fatal("Expected playlist, got nil") } if result.Kind != 42 { t.Errorf("Expected kind 42, got %d", result.Kind) } if result.Title != "My Playlist" { t.Errorf("Expected title 'My Playlist', got '%s'", result.Title) } if result.Description != "A test playlist" { t.Errorf("Expected description 'A test playlist', got '%s'", result.Description) } if result.TrackCount != 15 { t.Errorf("Expected track_count 15, got %d", result.TrackCount) } if result.Revision != 5 { t.Errorf("Expected revision 5, got %d", result.Revision) } if result.Owner == nil || result.Owner.Login != "testuser" { t.Errorf("Expected owner login 'testuser'") } if result.GetClient() != client { t.Error("Expected client to be set on playlist") } } func TestClient_GetPlaylist_NotFound(t *testing.T) { server := testServer(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write(playlistResponse([]map[string]any{})) }) defer server.Close() client := newTestClient(server.URL) result, err := client.GetPlaylist(context.Background(), 999) if err == nil { t.Fatal("Expected error for not found playlist") } if result != nil { t.Errorf("Expected nil playlist, got %v", result) } // Check that it's a not found error if err.Error() != "playlist not found" && err.Error() != "playlist not found: not found" { t.Errorf("Expected 'playlist not found' error, got: %v", err) } } func TestClient_GetPlaylist_Error(t *testing.T) { server := testServer(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) w.Header().Set("Content-Type", "application/json") w.Write(errorResponse("Internal server error")) }) defer server.Close() client := newTestClient(server.URL) _, err := client.GetPlaylist(context.Background(), 1) if err == nil { t.Fatal("Expected error") } if err.Error() == "" { t.Errorf("Expected non-empty error") } } func TestClient_CreatePlaylist(t *testing.T) { createdPlaylist := map[string]any{ "kind": 100, "title": "New Playlist", "description": "", "cover_uri": "", "created": "2024-01-01T00:00:00+03:00", "modified": "2024-01-01T00:00:00+03:00", "track_count": 0, "duration_ms": 0, "likes_count": 0, "is_public": true, "revision": 1, "uuid": "new-uuid-100", "tags": []any{}, "access_type": "full", "available": true, "content_warning": "", "genre": "", "owner_uid": 12345, "shared": false, "snapshot_id": "snap-new-100", "owner": map[string]any{ "uid": 12345, "login": "me", "display_name": "Me", "cover_uri": "", "sex": "", "public_playlists": 1, "followers_count": 0, "followings_count": 0, "has_playlists": true, "available": true, }, "tracks": []any{}, "similar_playlists": []any{}, } var capturedBody map[string]any server := testServer(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { t.Errorf("Expected POST method, got %s", r.Method) } if r.URL.Path != "/users/me/playlists" { t.Errorf("Expected path /users/me/playlists, got %s", r.URL.Path) } // Read and parse request body decoder := json.NewDecoder(r.Body) if err := decoder.Decode(&capturedBody); err != nil { t.Errorf("Failed to decode request body: %v", err) } w.Header().Set("Content-Type", "application/json") w.Write(singlePlaylistResponse(createdPlaylist)) }) defer server.Close() client := newTestClient(server.URL) result, err := client.CreatePlaylist(context.Background(), "New Playlist", "public") if err != nil { t.Fatalf("CreatePlaylist failed: %v", err) } if result == nil { t.Fatal("Expected playlist, got nil") } if result.Kind != 100 { t.Errorf("Expected kind 100, got %d", result.Kind) } if result.Title != "New Playlist" { t.Errorf("Expected title 'New Playlist', got '%s'", result.Title) } if result.IsPublic != true { t.Errorf("Expected is_public true, got %v", result.IsPublic) } if result.GetClient() != client { t.Error("Expected client to be set on playlist") } // Verify the request body if capturedBody == nil { t.Fatal("Request body not captured") } if capturedBody["title"] != "New Playlist" { t.Errorf("Expected title 'New Playlist' in body, got %v", capturedBody["title"]) } if capturedBody["visibility"] != "public" { t.Errorf("Expected visibility 'public' in body, got %v", capturedBody["visibility"]) } } func TestClient_CreatePlaylist_Private(t *testing.T) { createdPlaylist := map[string]any{ "kind": 101, "title": "Private Playlist", "description": "", "cover_uri": "", "created": "2024-01-01T00:00:00+03:00", "modified": "2024-01-01T00:00:00+03:00", "track_count": 0, "duration_ms": 0, "likes_count": 0, "is_public": false, "revision": 1, "uuid": "new-uuid-101", "tags": []any{}, "access_type": "full", "available": true, "content_warning": "", "genre": "", "owner_uid": 12345, "shared": false, "snapshot_id": "snap-new-101", "owner": map[string]any{ "uid": 12345, "login": "me", "display_name": "Me", "cover_uri": "", "sex": "", "public_playlists": 1, "followers_count": 0, "followings_count": 0, "has_playlists": true, "available": true, }, "tracks": []any{}, "similar_playlists": []any{}, } server := testServer(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write(singlePlaylistResponse(createdPlaylist)) }) defer server.Close() client := newTestClient(server.URL) result, err := client.CreatePlaylist(context.Background(), "Private Playlist", "private") if err != nil { t.Fatalf("CreatePlaylist failed: %v", err) } if result == nil { t.Fatal("Expected playlist, got nil") } if result.Title != "Private Playlist" { t.Errorf("Expected title 'Private Playlist', got '%s'", result.Title) } if result.IsPublic != false { t.Errorf("Expected is_public false for private playlist, got %v", result.IsPublic) } } func TestClient_CreatePlaylist_Error(t *testing.T) { server := testServer(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnauthorized) w.Header().Set("Content-Type", "application/json") w.Write(errorResponse("unauthorized")) }) defer server.Close() client := newTestClient(server.URL) _, err := client.CreatePlaylist(context.Background(), "Test", "public") if err == nil { t.Fatal("Expected error") } // Check that it's an unauthorized error if !utils.IsUnauthorizedError(err) { t.Errorf("Expected unauthorized error, got: %v", err) } }