import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const ORIGINAL_ENV = { ...process.env }; const mockSuccessResponse = { id: "chatcmpl-test", created: 1, model: "qwen3.5-plus", choices: [ { index: 0, message: { role: "assistant", content: "你好,我是测试响应。", }, finish_reason: "stop", }, ], }; describe("invokeLLM", () => { beforeEach(() => { vi.resetModules(); vi.restoreAllMocks(); process.env = { ...ORIGINAL_ENV }; }); afterEach(() => { process.env = { ...ORIGINAL_ENV }; vi.unstubAllGlobals(); }); it("uses LLM_* environment variables for request config", async () => { process.env.LLM_API_URL = "https://one.hao.work/v1/chat/completions"; process.env.LLM_API_KEY = "test-key"; process.env.LLM_MODEL = "qwen3.5-plus"; process.env.LLM_MAX_TOKENS = "4096"; process.env.LLM_ENABLE_THINKING = "0"; const fetchMock = vi.fn().mockResolvedValue({ ok: true, json: async () => mockSuccessResponse, }); vi.stubGlobal("fetch", fetchMock); const { invokeLLM } = await import("./llm"); await invokeLLM({ messages: [{ role: "user", content: "你好" }], }); expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledWith( "https://one.hao.work/v1/chat/completions", expect.objectContaining({ method: "POST", headers: expect.objectContaining({ authorization: "Bearer test-key", }), }) ); const [, request] = fetchMock.mock.calls[0] as [string, { body: string }]; expect(JSON.parse(request.body)).toMatchObject({ model: "qwen3.5-plus", max_tokens: 4096, messages: [{ role: "user", content: "你好" }], }); expect(JSON.parse(request.body)).not.toHaveProperty("thinking"); }); it("allows overriding the model per request", async () => { process.env.LLM_API_URL = "https://one.hao.work/v1/chat/completions"; process.env.LLM_API_KEY = "test-key"; process.env.LLM_MODEL = "qwen3.5-plus"; const fetchMock = vi.fn().mockResolvedValue({ ok: true, json: async () => mockSuccessResponse, }); vi.stubGlobal("fetch", fetchMock); const { invokeLLM } = await import("./llm"); await invokeLLM({ model: "qwen3-vl-235b-a22b", messages: [{ role: "user", content: "describe image" }], }); const [, request] = fetchMock.mock.calls[0] as [string, { body: string }]; expect(JSON.parse(request.body)).toMatchObject({ model: "qwen3-vl-235b-a22b", }); }); it("falls back to legacy forge variables when LLM_* values are absent", async () => { delete process.env.LLM_API_URL; delete process.env.LLM_API_KEY; delete process.env.LLM_MODEL; delete process.env.LLM_MAX_TOKENS; delete process.env.LLM_ENABLE_THINKING; delete process.env.LLM_THINKING_BUDGET; process.env.BUILT_IN_FORGE_API_URL = "https://forge.example.com"; process.env.BUILT_IN_FORGE_API_KEY = "legacy-key"; const fetchMock = vi.fn().mockResolvedValue({ ok: true, json: async () => mockSuccessResponse, }); vi.stubGlobal("fetch", fetchMock); const { invokeLLM } = await import("./llm"); await invokeLLM({ messages: [{ role: "user", content: "legacy" }], }); expect(fetchMock).toHaveBeenCalledWith( "https://forge.example.com/v1/chat/completions", expect.objectContaining({ headers: expect.objectContaining({ authorization: "Bearer legacy-key", }), }) ); const [, request] = fetchMock.mock.calls[0] as [string, { body: string }]; expect(JSON.parse(request.body)).toMatchObject({ model: "gemini-2.5-flash", }); }); });