


















@@ -0,0 +1,163 @@
1+import { beforeEach, describe, expect, it, vi } from "vitest";
2+3+const { fetchWithSsrFGuardMock } = vi.hoisted(() => ({
4+fetchWithSsrFGuardMock: vi.fn(),
5+}));
6+7+vi.mock("../../infra/net/fetch-guard.js", () => ({
8+fetchWithSsrFGuard: fetchWithSsrFGuardMock,
9+}));
10+11+import {
12+preflightCronModelProvider,
13+resetCronModelProviderPreflightCacheForTest,
14+} from "./model-preflight.runtime.js";
15+16+function mockReachableResponse(status = 200) {
17+fetchWithSsrFGuardMock.mockResolvedValueOnce({
18+response: { status },
19+release: vi.fn(async () => {}),
20+});
21+}
22+23+describe("preflightCronModelProvider", () => {
24+beforeEach(() => {
25+fetchWithSsrFGuardMock.mockReset();
26+resetCronModelProviderPreflightCacheForTest();
27+});
28+29+it("skips network checks for cloud provider URLs", async () => {
30+const result = await preflightCronModelProvider({
31+cfg: {
32+models: {
33+providers: {
34+openai: {
35+api: "openai-responses",
36+baseUrl: "https://api.openai.com/v1",
37+models: [],
38+},
39+},
40+},
41+},
42+provider: "openai",
43+model: "gpt-5.4",
44+});
45+46+expect(result).toEqual({ status: "available" });
47+expect(fetchWithSsrFGuardMock).not.toHaveBeenCalled();
48+});
49+50+it("treats any HTTP response from a local OpenAI-compatible endpoint as reachable", async () => {
51+mockReachableResponse(401);
52+53+const result = await preflightCronModelProvider({
54+cfg: {
55+models: {
56+providers: {
57+vllm: {
58+api: "openai-completions",
59+baseUrl: "http://127.0.0.1:8000/v1",
60+models: [],
61+},
62+},
63+},
64+},
65+provider: "vllm",
66+model: "llama",
67+});
68+69+expect(result).toEqual({ status: "available" });
70+expect(fetchWithSsrFGuardMock).toHaveBeenCalledWith(
71+expect.objectContaining({
72+url: "http://127.0.0.1:8000/v1/models",
73+timeoutMs: 2500,
74+}),
75+);
76+});
77+78+it("marks unreachable local Ollama endpoints unavailable and caches the result", async () => {
79+fetchWithSsrFGuardMock.mockRejectedValueOnce(new Error("ECONNREFUSED"));
80+81+const cfg = {
82+models: {
83+providers: {
84+Ollama: {
85+api: "ollama" as const,
86+baseUrl: "http://localhost:11434",
87+models: [],
88+},
89+},
90+},
91+};
92+const first = await preflightCronModelProvider({
93+ cfg,
94+provider: "ollama",
95+model: "qwen3:32b",
96+nowMs: 1000,
97+});
98+const second = await preflightCronModelProvider({
99+ cfg,
100+provider: "ollama",
101+model: "llama3.3:70b",
102+nowMs: 2000,
103+});
104+105+expect(first).toMatchObject({
106+status: "unavailable",
107+provider: "ollama",
108+model: "qwen3:32b",
109+baseUrl: "http://localhost:11434",
110+retryAfterMs: 300000,
111+});
112+expect(second).toMatchObject({
113+status: "unavailable",
114+provider: "ollama",
115+model: "llama3.3:70b",
116+baseUrl: "http://localhost:11434",
117+retryAfterMs: 300000,
118+});
119+expect(fetchWithSsrFGuardMock).toHaveBeenCalledTimes(1);
120+expect(fetchWithSsrFGuardMock).toHaveBeenCalledWith(
121+expect.objectContaining({
122+url: "http://localhost:11434/api/tags",
123+auditContext: "cron-model-provider-preflight",
124+}),
125+);
126+});
127+128+it("retries an unavailable endpoint after the cache ttl", async () => {
129+fetchWithSsrFGuardMock.mockRejectedValueOnce(new Error("ECONNREFUSED")).mockResolvedValueOnce({
130+response: { status: 200 },
131+release: vi.fn(async () => {}),
132+});
133+134+const cfg = {
135+models: {
136+providers: {
137+ollama: {
138+api: "ollama" as const,
139+baseUrl: "http://127.0.0.1:11434",
140+models: [],
141+},
142+},
143+},
144+};
145+146+const first = await preflightCronModelProvider({
147+ cfg,
148+provider: "ollama",
149+model: "llama3",
150+nowMs: 1000,
151+});
152+const second = await preflightCronModelProvider({
153+ cfg,
154+provider: "ollama",
155+model: "llama3",
156+nowMs: 1000 + 300001,
157+});
158+159+expect(first.status).toBe("unavailable");
160+expect(second).toEqual({ status: "available" });
161+expect(fetchWithSsrFGuardMock).toHaveBeenCalledTimes(2);
162+});
163+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。