diff --git a/ui/litellm-dashboard/e2e_tests/tests/modelsPage/credentials.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/modelsPage/credentials.spec.ts index 8b7824813a4..7c836068567 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/modelsPage/credentials.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/modelsPage/credentials.spec.ts @@ -38,7 +38,8 @@ test.describe("Edit LLM credential", () => { const row = page.locator("tr", { hasText: credentialName }); await expect(row).toBeVisible({ timeout: 15_000 }); - await row.getByRole("button").first().click(); + await row.getByTestId(`credential-actions-${credentialName}`).click(); + await page.getByTestId("credential-action-edit").click(); const modal = page.locator(".ant-modal-content").filter({ hasText: "Edit Credential" }); await expect(modal).toBeVisible({ timeout: 10_000 }); diff --git a/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/keys.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/keys.spec.ts index a55c19a53de..c44957ea737 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/keys.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/keys.spec.ts @@ -103,7 +103,8 @@ test.describe("Proxy Admin - Keys", () => { await expect(page.getByText("Back to Keys")).toBeVisible({ timeout: 10_000 }); - await page.getByRole("button", { name: "Delete Key" }).click(); + await page.getByRole("button", { name: "More key actions" }).click(); + await page.getByRole("menuitem", { name: "Delete Key" }).click(); const modal = page.locator(".ant-modal:visible"); await expect(modal).toBeVisible({ timeout: 5_000 }); diff --git a/ui/litellm-dashboard/src/components/model_add/CredentialsPanel.test.tsx b/ui/litellm-dashboard/src/components/model_add/CredentialsPanel.test.tsx index af38645b00d..93a015c8a36 100644 --- a/ui/litellm-dashboard/src/components/model_add/CredentialsPanel.test.tsx +++ b/ui/litellm-dashboard/src/components/model_add/CredentialsPanel.test.tsx @@ -4,7 +4,7 @@ import userEvent from "@testing-library/user-event"; import { UploadProps } from "antd/es/upload"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { CredentialItem, credentialCreateCall } from "@/components/networking"; +import { CredentialItem, credentialCreateCall, credentialUpdateCall } from "@/components/networking"; import NotificationsManager from "@/components/molecules/notifications_manager"; import CredentialsPanel from "./CredentialsPanel"; @@ -51,11 +51,17 @@ vi.mock("./CredentialModal", () => ({ if (!open) { return null; } + const values = + mode === "edit" + ? { + credential_name: "openai-key", + custom_llm_provider: "openai", + api_key: "sk-1****2345", + api_base: "https://proxy.e2e.example.com/v1", + } + : { credential_name: "new-cred", custom_llm_provider: "openai" }; return ( - ); @@ -179,6 +185,26 @@ describe("CredentialsPanel", () => { expect(NotificationsManager.success).not.toHaveBeenCalled(); }); + it("drops the masked api key from the update payload while keeping the edited api base", async () => { + const user = userEvent.setup(); + mockUseAuthorized.mockReturnValue({ accessToken: "test-token", userRole: "Admin" }); + mockUseCredentials.mockReturnValue({ data: { credentials }, isLoading: false, refetch: vi.fn() }); + vi.mocked(credentialUpdateCall).mockResolvedValueOnce(undefined as never); + + renderPanel(); + + await user.click(screen.getByTestId("credential-actions-openai-key")); + await user.click(await screen.findByTestId("credential-action-edit")); + await user.click(screen.getByTestId("credential-modal-edit-submit")); + + await waitFor(() => { + expect(credentialUpdateCall).toHaveBeenCalled(); + }); + const [, updatedName, payload] = vi.mocked(credentialUpdateCall).mock.calls[0]; + expect(updatedName).toBe("openai-key"); + expect(payload.credential_values).toEqual({ api_base: "https://proxy.e2e.example.com/v1" }); + }); + describe("Admin Viewer write-action gating", () => { // Admin Viewer can VIEW credentials but must not add / edit / delete them. it("hides the Add Credential button but still lists credentials", () => {