From 768fcaed2ecc786775dcb3a98a454f0576ab8dd0 Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Thu, 27 Aug 2026 21:01:40 -0700 Subject: [PATCH] fix(ui): restore the mid-dry-run submit guard and its regression tests --- .../add_model/add_auto_router_tab.test.tsx | 44 +++++++++++++++++++ .../add_model/add_auto_router_tab.tsx | 1 + 2 files changed, 45 insertions(+) diff --git a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx index 5304a4b2811..79fa2dab4f8 100644 --- a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx +++ b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx @@ -210,6 +210,29 @@ describe("AddAutoRouterTab", () => { expect(handleAddAutoRouterSubmit).not.toHaveBeenCalled(); }); + it("creates the router once when the form is submitted again mid dry-run", async () => { + vi.mocked(getMissingTiersError).mockReturnValue(null); + let resolveVerdict: (verdict: { valid: boolean }) => void = () => {}; + validateAutoRouterConfig.mockImplementationOnce( + () => + new Promise((resolve) => { + resolveVerdict = resolve; + }), + ); + + const { container } = renderWithProviders(); + fireEvent.change(screen.getByPlaceholderText(/smart_router/i), { target: { value: "double-submit-router" } }); + + fireEvent.submit(container.querySelector("form")!); + await waitFor(() => expect(screen.getByRole("button", { name: /add auto router/i })).toBeDisabled()); + fireEvent.submit(container.querySelector("form")!); + + resolveVerdict({ valid: true }); + await waitFor(() => expect(screen.getByRole("button", { name: /add auto router/i })).toBeEnabled()); + expect(validateAutoRouterConfig).toHaveBeenCalledTimes(1); + expect(handleAddAutoRouterSubmit).toHaveBeenCalledTimes(1); + }); + it("submits when the dry-run passes, so the gate is not simply blocking everything", async () => { const user = userEvent.setup(); vi.mocked(getMissingTiersError).mockReturnValue(null); @@ -652,6 +675,27 @@ describe("AddAutoRouterTab", () => { await waitFor(() => expect(toast.fromError).toHaveBeenCalledWith(expect.stringContaining("no longer available"))); expect(handleAddAutoRouterSubmit).not.toHaveBeenCalled(); }); + + it("carries a preset's per-tier reasoning effort through to the create payload", async () => { + const user = userEvent.setup(); + mockFetchAvailableModels.mockResolvedValue(ALL_FAMILY_MODELS); + + renderWithProviders(); + await waitForPresetEnabled("Anthropic Family"); + await selectTemplate("Anthropic Family"); + + await user.type(screen.getByPlaceholderText(/smart_router/i), "anthropic-router"); + await user.click(screen.getByRole("button", { name: /add auto router/i })); + + await waitFor(() => expect(handleAddAutoRouterSubmit).toHaveBeenCalled()); + expect(vi.mocked(handleAddAutoRouterSubmit).mock.calls.at(-1)?.[0]).toMatchObject({ + complexity_router_config: { + tier_model_configs: { + REASONING: [{ model_name: "claude-opus-5", litellm_params: { reasoning_effort: "high" } }], + }, + }, + }); + }); }); describe("default model pin", () => { diff --git a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx index 18a0c231899..df6b5334d55 100644 --- a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx +++ b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.tsx @@ -425,6 +425,7 @@ const AddAutoRouterTab: React.FC = ({ }; const handleAutoRouterSubmit = async () => { + if (isSubmitting) return; const name = form.getValues("auto_router_name"); if (!name) { setShowValidationErrors(true);