mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-29 06:33:28 +00:00
fix(ui): send the key's organization only when the edit form changes it
An untouched save from the key edit form now leaves organization_id out of the /key/update body, the same way the form already drops unchanged gated fields. This stops a save from writing a team's fallback organization onto a key that has none, and keeps the audit log free of no-op organization updates. The organization tests now use typed inputs
This commit is contained in:
parent
d712b29914
commit
b8e2192f4d
2 changed files with 48 additions and 25 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { toast } from "@/lib/toast";
|
||||
import type { KeyResponse } from "../key_team_helpers/key_list";
|
||||
|
||||
vi.mock("@/lib/toast", () => ({
|
||||
toast: { error: vi.fn(), success: vi.fn() },
|
||||
|
|
@ -657,40 +658,58 @@ describe("KeyInfoView handleKeyUpdate soft_budget", () => {
|
|||
});
|
||||
|
||||
describe("KeyInfoView handleKeyUpdate organization", () => {
|
||||
it("should show no organization after a save clears the one a key list row carried as org_id", async () => {
|
||||
const adminAuth = {
|
||||
accessToken: "access_abc",
|
||||
userId: "user_1",
|
||||
userRole: "Admin",
|
||||
premiumUser: true,
|
||||
token: "token_123",
|
||||
userEmail: "test@example.com",
|
||||
disabledPersonalKeyCreation: false,
|
||||
showSSOBanner: false,
|
||||
};
|
||||
keyUpdateCallMock.mockResolvedValueOnce({ ...baseKeyData, organization_id: null });
|
||||
const keyListRow = { ...baseKeyData, org_id: "org-1" } as KeyResponse;
|
||||
|
||||
const adminAuth = {
|
||||
accessToken: "access_abc",
|
||||
userId: "user_1",
|
||||
userRole: "Admin",
|
||||
premiumUser: true,
|
||||
token: "token_123",
|
||||
userEmail: "test@example.com",
|
||||
disabledPersonalKeyCreation: false,
|
||||
showSSOBanner: false,
|
||||
};
|
||||
|
||||
const renderKeyListRowSettings = () => {
|
||||
mockUseAuthorized.mockReturnValue(adminAuth);
|
||||
|
||||
render(
|
||||
<KeyInfoView
|
||||
keyId="tok_123"
|
||||
onClose={() => {}}
|
||||
keyData={{ ...baseKeyData, org_id: "org-1" } as any}
|
||||
onKeyDataUpdate={() => {}}
|
||||
teams={[]}
|
||||
/>,
|
||||
<KeyInfoView keyId="tok_123" onClose={() => {}} keyData={keyListRow} onKeyDataUpdate={() => {}} teams={[]} />,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText("Settings"));
|
||||
expect(screen.getByText("Organization", { selector: "p" }).nextElementSibling).toHaveTextContent("org-1");
|
||||
};
|
||||
|
||||
const submitEditForm = (formValues: Record<string, unknown>) => {
|
||||
fireEvent.click(screen.getByText("Edit Settings"));
|
||||
(globalThis as any).__TEST_FORM_VALUES = { token: "tok_123", organization_id: null };
|
||||
|
||||
Object.assign(globalThis, { __TEST_FORM_VALUES: formValues });
|
||||
fireEvent.click(screen.getByText("Mock Submit"));
|
||||
};
|
||||
|
||||
const shownOrganization = () => screen.getByText("Organization", { selector: "p" }).nextElementSibling;
|
||||
|
||||
it("should show no organization after a save clears the one a key list row carried as org_id", async () => {
|
||||
keyUpdateCallMock.mockResolvedValueOnce({ ...baseKeyData, organization_id: null });
|
||||
renderKeyListRowSettings();
|
||||
expect(shownOrganization()).toHaveTextContent("org-1");
|
||||
|
||||
submitEditForm({ token: "tok_123", organization_id: null });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Organization", { selector: "p" }).nextElementSibling).toHaveTextContent("Not Set");
|
||||
expect(shownOrganization()).toHaveTextContent("Not Set");
|
||||
});
|
||||
expect(keyUpdateCallMock.mock.calls[0][1]).toHaveProperty("organization_id", null);
|
||||
});
|
||||
|
||||
it("should leave organization_id out of a save that keeps the key's organization", async () => {
|
||||
keyUpdateCallMock.mockResolvedValueOnce({ ...baseKeyData, organization_id: "org-1" });
|
||||
renderKeyListRowSettings();
|
||||
|
||||
submitEditForm({ token: "tok_123", organization_id: "org-1" });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(keyUpdateCallMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(keyUpdateCallMock.mock.calls[0][1]).not.toHaveProperty("organization_id");
|
||||
expect(shownOrganization()).toHaveTextContent("org-1");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -240,6 +240,10 @@ export default function KeyInfoView({
|
|||
delete formValues.disable_global_guardrails;
|
||||
}
|
||||
|
||||
if ((formValues.organization_id || null) === keyOrganizationId(currentKeyData)) {
|
||||
delete formValues.organization_id;
|
||||
}
|
||||
|
||||
// Handle max budget empty string
|
||||
formValues.max_budget = mapEmptyStringToNull(formValues.max_budget);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue