
























@@ -398,28 +398,77 @@ describe("ollama setup", () => {
398398399399describe("ensureOllamaModelPulled", () => {
400400it("pulls model when not available locally", async () => {
401-const progress = { update: vi.fn(), stop: vi.fn() };
402-const prompter = {
403-progress: vi.fn(() => progress),
404-} as unknown as WizardPrompter;
405-406-const fetchMock = createOllamaFetchMock({
407-tags: ["llama3:8b"],
408-pullResponse: new Response('{"status":"success"}\n', { status: 200 }),
409-});
410-vi.stubGlobal("fetch", fetchMock);
411-412-await ensureOllamaModelPulled({
413-config: createDefaultOllamaConfig("ollama/gemma4"),
414-model: "ollama/gemma4",
415- prompter,
416-});
401+vi.useFakeTimers();
402+try {
403+const progress = { update: vi.fn(), stop: vi.fn() };
404+const prompter = {
405+progress: vi.fn(() => progress),
406+} as unknown as WizardPrompter;
407+408+const fetchMock = createOllamaFetchMock({
409+tags: ["llama3:8b"],
410+pullResponse: new Response('{"status":"success"}\n', { status: 200 }),
411+});
412+vi.stubGlobal("fetch", fetchMock);
413+414+await ensureOllamaModelPulled({
415+config: createDefaultOllamaConfig("ollama/gemma4"),
416+model: "ollama/gemma4",
417+ prompter,
418+});
419+420+expect(fetchMock).toHaveBeenCalledTimes(2);
421+expect(fetchMock.mock.calls[1][0]).toContain("/api/pull");
422+const pullInit = fetchMock.mock.calls[1][1];
423+expect(pullInit?.signal).toBeInstanceOf(AbortSignal);
424+expect(pullInit?.signal?.aborted).toBe(false);
425+426+await vi.advanceTimersByTimeAsync(30_000);
427+expect(pullInit?.signal?.aborted).toBe(false);
428+} finally {
429+vi.useRealTimers();
430+}
431+});
417432418-expect(fetchMock).toHaveBeenCalledTimes(2);
419-expect(fetchMock.mock.calls[1][0]).toContain("/api/pull");
420-const pullInit = fetchMock.mock.calls[1][1];
421-expect(pullInit?.signal).toBeInstanceOf(AbortSignal);
422-expect(pullInit?.signal?.aborted).toBe(false);
433+it("fails stalled model pull streams after an idle timeout", async () => {
434+vi.useFakeTimers();
435+try {
436+const progress = { update: vi.fn(), stop: vi.fn() };
437+const prompter = {
438+progress: vi.fn(() => progress),
439+} as unknown as WizardPrompter;
440+const fetchMock = vi.fn(async (input: string | URL | Request) => {
441+const url = requestUrl(input);
442+if (url.endsWith("/api/tags")) {
443+return jsonResponse({ models: [] });
444+}
445+if (url.endsWith("/api/pull")) {
446+return new Response(new ReadableStream<Uint8Array>(), { status: 200 });
447+}
448+throw new Error(`Unexpected fetch: ${url}`);
449+});
450+vi.stubGlobal("fetch", fetchMock);
451+452+const pullPromise = ensureOllamaModelPulled({
453+config: createDefaultOllamaConfig("ollama/gemma4"),
454+model: "ollama/gemma4",
455+ prompter,
456+}).catch((err: unknown) => err);
457+458+for (let attempts = 0; attempts < 50 && fetchMock.mock.calls.length < 2; attempts += 1) {
459+await vi.advanceTimersByTimeAsync(0);
460+await Promise.resolve();
461+}
462+expect(fetchMock.mock.calls[1]?.[0]).toContain("/api/pull");
463+464+await vi.advanceTimersByTimeAsync(300_000);
465+await expect(pullPromise).resolves.toEqual(
466+expect.objectContaining({ message: "Failed to download selected Ollama model" }),
467+);
468+expect(progress.stop).toHaveBeenCalledWith(expect.stringContaining("Ollama pull stalled"));
469+} finally {
470+vi.useRealTimers();
471+}
423472});
424473425474it("skips pull when model is already available", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。