diff --git a/ui/litellm-dashboard/src/components/networking.test.ts b/ui/litellm-dashboard/src/components/networking.test.ts index 3c107fa586..05aa2d5162 100644 --- a/ui/litellm-dashboard/src/components/networking.test.ts +++ b/ui/litellm-dashboard/src/components/networking.test.ts @@ -404,3 +404,52 @@ describe("individualModelHealthCheckCall", () => { expect(parsed.searchParams.get("model_id")).toBe("id/with/slashes"); }); }); + +describe("teamInfoCall", () => { + const originalFetch = global.fetch; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + global.fetch = originalFetch; + }); + + it("should URL-encode team_id query param to handle special characters safely", async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: vi.fn().mockResolvedValue({ team_id: "team with spaces & special?chars" }), + } as any); + global.fetch = mockFetch as any; + + const teamID = "team with spaces & special?chars"; + await Networking.teamInfoCall("token", teamID); + + expect(mockFetch).toHaveBeenCalledOnce(); + const [url] = mockFetch.mock.calls[0]; + const urlStr = typeof url === "string" ? url : (url as Request).url; + const parsed = typeof url === "string" ? new URL(url, "http://example.com") : new URL((url as Request).url); + + expect(urlStr).toContain("/team/info"); + // Encoded value is present in the raw URL string (verifies encodeURIComponent was used) + expect(urlStr).toContain(`team_id=${encodeURIComponent(teamID)}`); + // Round-trip parse returns the original team_id + expect(parsed.searchParams.get("team_id")).toBe(teamID); + }); + + it("should not append team_id when teamID is null", async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: vi.fn().mockResolvedValue({}), + } as any); + global.fetch = mockFetch as any; + + await Networking.teamInfoCall("token", null); + + expect(mockFetch).toHaveBeenCalledOnce(); + const [url] = mockFetch.mock.calls[0]; + const parsed = typeof url === "string" ? new URL(url, "http://example.com") : new URL((url as Request).url); + expect(parsed.searchParams.has("team_id")).toBe(false); + }); +}); diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 44208904a7..8bfe7c9869 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -1386,7 +1386,7 @@ export const teamInfoCall = async (accessToken: string, teamID: string | null) = try { let url = proxyBaseUrl ? `${proxyBaseUrl}/team/info` : `/team/info`; if (teamID) { - url = `${url}?team_id=${teamID}`; + url = `${url}?team_id=${encodeURIComponent(teamID)}`; } console.log("in teamInfoCall"); const response = await fetch(url, {