mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
fix(ui): read accessToken through a ref at the submit-time create call
submitRecommendedRouter awaits a network round trip (the fresh preset re-check) before calling handleAddAutoRouterSubmit, so the accessToken it closed over at click time could be stale by the time that call fires if the token rotates during the wait. Read it through a ref kept in sync via useEffect instead, so the create call always uses whatever token is current when it actually runs.
This commit is contained in:
parent
b61da43e02
commit
6ae0be8874
2 changed files with 41 additions and 2 deletions
|
|
@ -289,6 +289,38 @@ describe("AddAutoRouterTab", () => {
|
|||
expect(mockHandleAddAutoRouterSubmit).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// submitRecommendedRouter awaits a network round trip (the submit-time re-check) before it ever
|
||||
// reaches the create call. If accessToken rotates while that await is pending, a closure-captured
|
||||
// value would carry the token that was live at click time into a create call that fires after the
|
||||
// replacement token is active. Read it through a ref updated every render instead.
|
||||
it("uses the current access token for creation even if it rotates while the submit-time re-check is in flight", async () => {
|
||||
const user = userEvent.setup();
|
||||
let resolveInitialRecheck: (models: ModelGroup[]) => void = () => undefined;
|
||||
mockFetchAvailableModels
|
||||
.mockResolvedValueOnce(ALL_FAMILY_MODELS)
|
||||
.mockReturnValueOnce(new Promise<ModelGroup[]>((resolve) => (resolveInitialRecheck = resolve)))
|
||||
.mockResolvedValueOnce(ALL_FAMILY_MODELS);
|
||||
|
||||
const { rerender } = renderWithProviders(
|
||||
<AddAutoRouterTab handleOk={vi.fn()} accessToken="stale-token" userRole="Admin" />,
|
||||
);
|
||||
openTemplateDropdown();
|
||||
await waitFor(() => expect(isOptionDisabled(optionByLabel("Anthropic Family")!)).toBe(false));
|
||||
fireEvent.click(optionByLabel("Anthropic Family")!);
|
||||
|
||||
await user.type(screen.getByPlaceholderText(/smart_router/i), "rotated-token-router");
|
||||
await user.click(screen.getByRole("button", { name: /add auto router/i }));
|
||||
await waitFor(() => expect(mockFetchAvailableModels).toHaveBeenCalledTimes(2));
|
||||
|
||||
// The token rotates while the re-check above is still in flight.
|
||||
rerender(<AddAutoRouterTab handleOk={vi.fn()} accessToken="fresh-token" userRole="Admin" />);
|
||||
|
||||
resolveInitialRecheck(ALL_FAMILY_MODELS);
|
||||
|
||||
await waitFor(() => expect(mockHandleAddAutoRouterSubmit).toHaveBeenCalled());
|
||||
expect(mockHandleAddAutoRouterSubmit.mock.calls.at(-1)?.[1]).toBe("fresh-token");
|
||||
});
|
||||
|
||||
// The headline behavior: selecting a preset must pre-fill the tier config so the created
|
||||
// router carries the preset's models. Real tier validation runs here (getMissingTiersError is
|
||||
// not stubbed), so if selection stopped pre-filling, the empty tiers would either block the
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Card, Form, Button, Tooltip, Typography, Select as AntdSelect, Modal } from "antd";
|
||||
import { TextInput } from "@tremor/react";
|
||||
|
|
@ -74,6 +74,13 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
|
|||
createScope = "unscoped-ok",
|
||||
}) => {
|
||||
const requiresTeamScope = createScope === "team-required";
|
||||
// submitRecommendedRouter awaits a network round trip before creating the router; reading
|
||||
// accessToken through this ref instead of the closure keeps that call on whatever token is
|
||||
// current when it actually fires, not whichever one was live when submit was clicked.
|
||||
const accessTokenRef = useRef(accessToken);
|
||||
useEffect(() => {
|
||||
accessTokenRef.current = accessToken;
|
||||
}, [accessToken]);
|
||||
const [form] = Form.useForm();
|
||||
const [modelAccessGroups, setModelAccessGroups] = useState<string[]>([]);
|
||||
|
||||
|
|
@ -300,7 +307,7 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
|
|||
model_access_group: form.getFieldValue("model_access_group"),
|
||||
};
|
||||
|
||||
await handleAddAutoRouterSubmit(submitValues, accessToken, form, handleOk);
|
||||
await handleAddAutoRouterSubmit(submitValues, accessTokenRef.current, form, handleOk);
|
||||
} catch (error) {
|
||||
console.error("Validation failed:", error);
|
||||
NotificationManager.fromBackend("Please fill in all required fields");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue