Skip to content

Commit 3c44f8d

Browse files
committed
fix(ui): surface toggle failures on the block-unpriced-models setting
The hook swallowed errors into the console, so an admin flipping the switch without STORE_MODEL_IN_DB saw nothing happen and got no reason why. Adds the missing hook tests.
1 parent 3672fa9 commit 3c44f8d

2 files changed

Lines changed: 110 additions & 0 deletions

File tree

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { describe, it, expect, vi, beforeEach } from "vitest";
2+
import { renderHook, act } from "@testing-library/react";
3+
import { useBlockUnpricedConfig } from "./use_block_unpriced_config";
4+
import { apiClient } from "@/components/networking";
5+
import { toast } from "@/lib/toast";
6+
7+
vi.mock("@/components/networking", () => ({
8+
apiClient: {
9+
get: vi.fn(),
10+
patch: vi.fn(),
11+
},
12+
}));
13+
14+
const ENDPOINT = "/config/block_requests_for_models_without_pricing";
15+
16+
describe("useBlockUnpricedConfig", () => {
17+
beforeEach(() => {
18+
vi.clearAllMocks();
19+
});
20+
21+
describe("fetchBlockUnpriced", () => {
22+
it("reflects the enabled flag returned by the proxy", async () => {
23+
vi.mocked(apiClient.get).mockResolvedValueOnce({ enabled: true });
24+
25+
const { result } = renderHook(() => useBlockUnpricedConfig({ accessToken: "test-token" }));
26+
27+
await act(async () => {
28+
await result.current.fetchBlockUnpriced();
29+
});
30+
31+
expect(apiClient.get).toHaveBeenCalledWith(ENDPOINT, { accessToken: "test-token" });
32+
expect(result.current.blockUnpriced).toBe(true);
33+
});
34+
35+
it("surfaces a toast when the fetch throws", async () => {
36+
const error = new Error("Network error");
37+
vi.mocked(apiClient.get).mockRejectedValueOnce(error);
38+
39+
const { result } = renderHook(() => useBlockUnpricedConfig({ accessToken: "test-token" }));
40+
41+
await act(async () => {
42+
await result.current.fetchBlockUnpriced();
43+
});
44+
45+
expect(toast.fromError).toHaveBeenCalledWith(error);
46+
expect(result.current.blockUnpriced).toBe(false);
47+
});
48+
49+
it("does nothing without an access token", async () => {
50+
const { result } = renderHook(() => useBlockUnpricedConfig({ accessToken: null }));
51+
52+
await act(async () => {
53+
await result.current.fetchBlockUnpriced();
54+
});
55+
56+
expect(apiClient.get).not.toHaveBeenCalled();
57+
});
58+
});
59+
60+
describe("setBlockUnpriced", () => {
61+
it("persists the new value and confirms it with a toast", async () => {
62+
vi.mocked(apiClient.patch).mockResolvedValueOnce({ enabled: true });
63+
64+
const { result } = renderHook(() => useBlockUnpricedConfig({ accessToken: "test-token" }));
65+
66+
await act(async () => {
67+
await result.current.setBlockUnpriced(true);
68+
});
69+
70+
expect(apiClient.patch).toHaveBeenCalledWith(ENDPOINT, {
71+
accessToken: "test-token",
72+
body: { enabled: true },
73+
});
74+
expect(result.current.blockUnpriced).toBe(true);
75+
expect(toast.success).toHaveBeenCalledWith(expect.stringMatching(/will now be blocked/i));
76+
expect(result.current.isUpdating).toBe(false);
77+
});
78+
79+
it("confirms turning the block back off", async () => {
80+
vi.mocked(apiClient.patch).mockResolvedValueOnce({ enabled: false });
81+
82+
const { result } = renderHook(() => useBlockUnpricedConfig({ accessToken: "test-token" }));
83+
84+
await act(async () => {
85+
await result.current.setBlockUnpriced(false);
86+
});
87+
88+
expect(result.current.blockUnpriced).toBe(false);
89+
expect(toast.success).toHaveBeenCalledWith(expect.stringMatching(/now allowed/i));
90+
});
91+
92+
it("surfaces the proxy error and leaves the flag unchanged when the update fails", async () => {
93+
const error = new Error("Set `'STORE_MODEL_IN_DB='True'` in your env to enable this feature.");
94+
vi.mocked(apiClient.patch).mockRejectedValueOnce(error);
95+
96+
const { result } = renderHook(() => useBlockUnpricedConfig({ accessToken: "test-token" }));
97+
98+
await act(async () => {
99+
await result.current.setBlockUnpriced(true);
100+
});
101+
102+
expect(toast.fromError).toHaveBeenCalledWith(error);
103+
expect(toast.success).not.toHaveBeenCalled();
104+
expect(result.current.blockUnpriced).toBe(false);
105+
expect(result.current.isUpdating).toBe(false);
106+
});
107+
});
108+
});

ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/use_block_unpriced_config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export function useBlockUnpricedConfig({ accessToken }: UseBlockUnpricedConfigPr
3030
setBlockUnpricedState(Boolean(data?.enabled));
3131
} catch (error) {
3232
console.error("Error fetching block-unpriced-models setting:", error);
33+
toast.fromError(error);
3334
}
3435
}, [accessToken]);
3536

@@ -47,6 +48,7 @@ export function useBlockUnpricedConfig({ accessToken }: UseBlockUnpricedConfigPr
4748
);
4849
} catch (error) {
4950
console.error("Error updating block-unpriced-models setting:", error);
51+
toast.fromError(error);
5052
} finally {
5153
setIsUpdating(false);
5254
}

0 commit comments

Comments
 (0)