






















1+import { mkdtempSync, rmSync } from "node:fs";
2+import { tmpdir } from "node:os";
3+import { join } from "node:path";
4+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5+6+const fetchWithSsrFGuardMock = vi.hoisted(() => vi.fn());
7+const spawnSyncMock = vi.hoisted(() => vi.fn());
8+9+vi.mock("../../infra/net/fetch-guard.js", () => ({
10+fetchWithSsrFGuard: fetchWithSsrFGuardMock,
11+}));
12+13+vi.mock("node:child_process", () => ({
14+spawnSync: spawnSyncMock,
15+}));
16+17+let originalAgentDir: string | undefined;
18+let tempAgentDir: string | undefined;
19+20+beforeEach(() => {
21+originalAgentDir = process.env.OPENCLAW_AGENT_DIR;
22+tempAgentDir = mkdtempSync(join(tmpdir(), "openclaw-tools-manager-"));
23+process.env.OPENCLAW_AGENT_DIR = tempAgentDir;
24+fetchWithSsrFGuardMock.mockReset();
25+spawnSyncMock.mockReturnValue({
26+error: new Error("ENOENT"),
27+status: null,
28+stderr: Buffer.alloc(0),
29+stdout: Buffer.alloc(0),
30+});
31+});
32+33+afterEach(() => {
34+vi.clearAllMocks();
35+vi.resetModules();
36+if (originalAgentDir === undefined) {
37+delete process.env.OPENCLAW_AGENT_DIR;
38+} else {
39+process.env.OPENCLAW_AGENT_DIR = originalAgentDir;
40+}
41+if (tempAgentDir) {
42+rmSync(tempAgentDir, { recursive: true, force: true });
43+}
44+tempAgentDir = undefined;
45+});
46+47+describe("ensureTool", () => {
48+it("cancels release-check error bodies before releasing guarded fetches", async () => {
49+const { ensureTool } = await import("./tools-manager.js");
50+const release = vi.fn(async () => {});
51+const response = new Response("server error", { status: 503 });
52+const cancel = vi.spyOn(response.body!, "cancel").mockResolvedValue(undefined);
53+fetchWithSsrFGuardMock.mockResolvedValueOnce({
54+ response,
55+ release,
56+finalUrl: "https://api.github.com/repos/sharkdp/fd/releases/latest",
57+});
58+59+await expect(ensureTool("fd", true)).resolves.toBeUndefined();
60+61+expect(cancel).toHaveBeenCalledOnce();
62+expect(release).toHaveBeenCalledOnce();
63+});
64+65+it("cancels download error bodies before releasing guarded fetches", async () => {
66+const { ensureTool } = await import("./tools-manager.js");
67+const releaseCheckRelease = vi.fn(async () => {});
68+const downloadRelease = vi.fn(async () => {});
69+const downloadResponse = new Response("missing asset", { status: 404 });
70+const cancel = vi.spyOn(downloadResponse.body!, "cancel").mockResolvedValue(undefined);
71+fetchWithSsrFGuardMock
72+.mockResolvedValueOnce({
73+response: new Response(JSON.stringify({ tag_name: "14.1.1" }), { status: 200 }),
74+release: releaseCheckRelease,
75+finalUrl: "https://api.github.com/repos/BurntSushi/ripgrep/releases/latest",
76+})
77+.mockResolvedValueOnce({
78+response: downloadResponse,
79+release: downloadRelease,
80+finalUrl: "https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/archive",
81+});
82+83+await expect(ensureTool("rg", true)).resolves.toBeUndefined();
84+85+expect(cancel).toHaveBeenCalledOnce();
86+expect(releaseCheckRelease).toHaveBeenCalledOnce();
87+expect(downloadRelease).toHaveBeenCalledOnce();
88+});
89+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。