/** * @file ProxyPicker.test.tsx * @description The per-session AI Proxy picker: Native plus the saved * profiles, Native alone and disabled on a tool that cannot use a proxy, * remembering the pick, and falling back to Native when the remembered * profile is gone; plus `proxyArg`, which never sends native. * @author Nguyễn Ngọc Trí Vĩ */ import { act, render, renderHook, screen, waitFor } from "@testing-library/react"; import type { AiProxy } from "../../../lib/api"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { ProxyPicker, proxyArg, useAiProxies, useSessionProxy } from "../ProxyPicker"; import { api } from "../../../lib/api"; vi.mock("../../../lib/api", async (importOriginal) => { const actual = await importOriginal>(); return { ...actual, api: { aiTools: { list: vi.fn().mockResolvedValue({ default: "claude", tools: [ { id: "claude", label: "Claude Code", proxyCapable: true }, { id: "gemini", label: "Gemini", proxyCapable: false }, ].map((x) => ({ ...x, installed: true, enabled: true })), }), }, aiProxies: { list: vi.fn() }, }, }; }); const native = { name: "native", label: "Native", builtin: true } as AiProxy; const nine = { name: "9router", label: "9Router", builtin: false } as AiProxy; beforeEach(() => { localStorage.clear(); vi.mocked(api.aiProxies.list) .mockReset() .mockResolvedValue({ items: [native, nine] } as never); }); describe("ProxyPicker", () => { it("lists the saved profiles for a tool that can use one", async () => { render( {}} /> ); expect(await screen.findByText("9Router")).toBeInTheDocument(); }); it("shows Native, disabled, on a tool with no endpoint to point at a proxy", () => { render( {}} /> ); const picker = screen.getByTestId("proxy-picker"); expect(picker.querySelector("button")).toBeDisabled(); expect(picker).not.toHaveTextContent("9Router"); }); it("falls back to Native when the remembered profile is gone", async () => { const onChange = vi.fn(); render(); await waitFor(() => expect(onChange).toHaveBeenCalledWith("native")); }); it("useAiProxies reads the saved profiles once", async () => { const { result } = renderHook(() => useAiProxies()); await waitFor(() => expect(result.current).toEqual([native, nine])); expect(api.aiProxies.list).toHaveBeenCalledTimes(1); }); }); describe("useSessionProxy / proxyArg", () => { it("remembers the last pick for the next session", () => { const first = renderHook(() => useSessionProxy()); expect(first.result.current[0]).toBe("native"); act(() => first.result.current[1]("9router")); expect(renderHook(() => useSessionProxy()).result.current[0]).toBe("9router"); }); it("still reads the pick remembered before the rename", () => { localStorage.setItem("agenthub.session.router", "9router"); expect(renderHook(() => useSessionProxy()).result.current[0]).toBe("9router"); }); it("sends a proxy only for a capable tool and never native", () => { expect(proxyArg(true, "9router")).toEqual({ proxy: "9router" }); expect(proxyArg(true, "native")).toEqual({}); expect(proxyArg(false, "9router")).toEqual({}); }); });