| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- import { afterEach, describe, expect, it, vi } from "vitest"
- import type { LlmConfig } from "@/stores/wiki-store"
- import { invoke } from "@tauri-apps/api/core"
- const fetchMock = vi.fn()
- vi.mock("@/lib/tauri-fetch", () => ({
- getHttpFetch: async () => fetchMock,
- }))
- vi.mock("@tauri-apps/api/core", () => ({
- invoke: vi.fn(),
- }))
- vi.mock("@/lib/platform", () => ({
- isTauri: () => true,
- }))
- function customConfig(overrides: Partial<LlmConfig> = {}): LlmConfig {
- return {
- provider: "custom",
- apiKey: "sk-test",
- model: "gpt-4o",
- ollamaUrl: "http://localhost:11434",
- customEndpoint: "https://hub.linux.do/v1",
- maxContextSize: 128000,
- apiMode: "chat_completions",
- reasoning: { mode: "off" },
- ...overrides,
- }
- }
- afterEach(() => {
- fetchMock.mockReset()
- vi.mocked(invoke).mockReset()
- })
- describe("settings model list", () => {
- it("fetches custom OpenAI-compatible models from the normalized /models endpoint", async () => {
- fetchMock.mockResolvedValueOnce(
- new Response(JSON.stringify({ data: [{ id: "gpt-test" }] }), {
- status: 200,
- headers: { "Content-Type": "application/json" },
- }),
- )
- const { fetchLlmModelList } = await import("./settings-model-list")
- const result = await fetchLlmModelList(customConfig())
- expect(fetchMock).toHaveBeenCalledWith("https://hub.linux.do/v1/models", {
- method: "GET",
- headers: {
- Authorization: "Bearer sk-test",
- Origin: "",
- },
- })
- expect(result.models).toEqual(["gpt-test"])
- })
- it("retries model list 403 responses with browser-compatible OpenAI headers", async () => {
- fetchMock
- .mockResolvedValueOnce(new Response("forbidden", { status: 403 }))
- .mockResolvedValueOnce(
- new Response(JSON.stringify({ data: [{ id: "linux-do-model" }] }), {
- status: 200,
- headers: { "Content-Type": "application/json" },
- }),
- )
- const { fetchLlmModelList } = await import("./settings-model-list")
- const result = await fetchLlmModelList(customConfig())
- expect(fetchMock).toHaveBeenCalledTimes(2)
- expect(fetchMock.mock.calls[1]).toEqual([
- "https://hub.linux.do/v1/models",
- {
- method: "GET",
- headers: expect.objectContaining({
- Authorization: "Bearer sk-test",
- Accept: "application/json",
- "User-Agent": expect.stringContaining("Mozilla/5.0"),
- }),
- },
- ])
- expect(result.models).toEqual(["linux-do-model"])
- })
- it("keeps the original 403 diagnostic when the compatibility retry cannot be sent", async () => {
- fetchMock
- .mockResolvedValueOnce(new Response("forbidden", { status: 403 }))
- .mockRejectedValueOnce(new TypeError("Refused to set unsafe header"))
- const { fetchLlmModelList } = await import("./settings-model-list")
- await expect(fetchLlmModelList(customConfig())).rejects.toThrow(
- "模型列表拉取失败:HTTP 403 forbidden",
- )
- })
- it("reads the configured local Claude CLI model from Tauri detection", async () => {
- vi.mocked(invoke).mockResolvedValueOnce({
- installed: true,
- version: "2.1.169 (Claude Code)",
- path: "C:/Users/Administrator/AppData/Roaming/npm/claude.cmd",
- model: "haiku",
- error: null,
- })
- const { fetchLlmModelList } = await import("./settings-model-list")
- const result = await fetchLlmModelList(customConfig({
- provider: "claude-code",
- apiKey: "",
- model: "",
- }))
- expect(invoke).toHaveBeenCalledWith("claude_cli_detect")
- expect(result.models).toEqual(["haiku"])
- })
- it("reads the configured local Codex CLI model from Tauri detection", async () => {
- vi.mocked(invoke).mockResolvedValueOnce({
- installed: true,
- version: "codex-cli 0.146.1",
- path: "C:/Users/Administrator/AppData/Roaming/npm/codex.cmd",
- model: "gpt-5.6-terra",
- appServerReady: true,
- dynamicToolsReady: true,
- models: ["gpt-5.6-terra", "gpt-5.6-sol", "gpt-5.6-luna"],
- error: null,
- })
- const { fetchLlmModelList } = await import("./settings-model-list")
- const result = await fetchLlmModelList(customConfig({
- provider: "codex-cli",
- apiKey: "",
- model: "",
- }))
- expect(invoke).toHaveBeenCalledWith("codex_cli_detect")
- expect(result.models).toEqual(["gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra"])
- })
- it("rejects a Codex CLI without app-server dynamic tools", async () => {
- vi.mocked(invoke).mockResolvedValueOnce({
- installed: true,
- version: "codex-cli 0.120.0",
- path: "/usr/local/bin/codex",
- appServerReady: false,
- dynamicToolsReady: false,
- models: [],
- error: "当前 Codex CLI 不支持 QMAI 主 Agent,请升级 Codex CLI。",
- })
- const { fetchLlmModelList } = await import("./settings-model-list")
- await expect(fetchLlmModelList(customConfig({
- provider: "codex-cli",
- apiKey: "",
- model: "",
- }))).rejects.toThrow("请升级 Codex CLI")
- })
- })
|