From 5beb78fd1c1c2b627a68c5371609b3486a03829f Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Tue, 18 Aug 2026 17:31:32 -0700 Subject: [PATCH] test(ui): pin the key edit payload paths fed by component state Most of the key edit payload is bound form fields, and the existing contract test pins those with a strict equality against a fixed object. Two keys are not fields: budget_fallbacks and tag_rpm_limit are computed in the submit handler from React state seeded off the loaded key, so a rewrite that moves that state can drop them without failing anything. budget_fallbacks had no coverage at all and tag_rpm_limit only appeared in the untouched-save constant, where it is an empty map either way. Pins that stored fallbacks and stored per-tag limits both survive an untouched save, and that a key with no fallbacks omits the field rather than sending an empty map, since the backend treats those differently. Verified by mutation: dropping the budget_fallbacks assignment reddens the resend case while correctly leaving the omit case green, and dropping the tag_rpm_limit assignment reddens its own case. --- .../templates/key_edit_view.test.tsx | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/ui/litellm-dashboard/src/components/templates/key_edit_view.test.tsx b/ui/litellm-dashboard/src/components/templates/key_edit_view.test.tsx index 4a3fd50b379..36fddbe5c0f 100644 --- a/ui/litellm-dashboard/src/components/templates/key_edit_view.test.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_edit_view.test.tsx @@ -1777,5 +1777,50 @@ describe("KeyEditView", () => { }); expect(onSubmitMock.mock.calls[0][0]).toHaveProperty("duration", null); }); + + it("resends stored budget fallbacks on an untouched save", async () => { + const onSubmitMock = vi.fn().mockResolvedValue(undefined); + renderForPayload(onSubmitMock, { + ...MOCK_KEY_DATA, + budget_fallbacks: { "gpt-4": ["gpt-4o-mini"] }, + } as KeyResponse); + await screen.findByRole("button", { name: /save changes/i }); + + await userEvent.click(screen.getByRole("button", { name: /save changes/i })); + + await waitFor(() => { + expect(onSubmitMock).toHaveBeenCalled(); + }); + expect(onSubmitMock.mock.calls[0][0]).toHaveProperty("budget_fallbacks", { "gpt-4": ["gpt-4o-mini"] }); + }); + + it("omits budget fallbacks entirely for a key that has none", async () => { + const onSubmitMock = vi.fn().mockResolvedValue(undefined); + renderForPayload(onSubmitMock); + await screen.findByRole("button", { name: /save changes/i }); + + await userEvent.click(screen.getByRole("button", { name: /save changes/i })); + + await waitFor(() => { + expect(onSubmitMock).toHaveBeenCalled(); + }); + expect(onSubmitMock.mock.calls[0][0]).not.toHaveProperty("budget_fallbacks"); + }); + + it("resends the stored per-tag rpm limits on an untouched save", async () => { + const onSubmitMock = vi.fn().mockResolvedValue(undefined); + renderForPayload(onSubmitMock, { + ...MOCK_KEY_DATA, + metadata: { ...MOCK_KEY_DATA.metadata, tag_rpm_limit: { "test-tag": 7 } }, + } as KeyResponse); + await screen.findByRole("button", { name: /save changes/i }); + + await userEvent.click(screen.getByRole("button", { name: /save changes/i })); + + await waitFor(() => { + expect(onSubmitMock).toHaveBeenCalled(); + }); + expect(onSubmitMock.mock.calls[0][0]).toHaveProperty("tag_rpm_limit", { "test-tag": 7 }); + }); }); });