Spaces:
Build error
Build error
Download plugins/openclaw/test/proxy-manager.test.ts from minhtudragon/headroom_3: direct link, hf CLI and curl.
- Browser
- Download file 9.34 kB
-
https://huggingface.co/spaces/minhtudragon/headroom_3/resolve/88e38edf9a06c75ad12f39e1287f5c1d6059190b/plugins/openclaw/test/proxy-manager.test.ts
- Command line
-
hf download hf://spaces/minhtudragon/headroom_3@88e38edf9a06c75ad12f39e1287f5c1d6059190b/plugins/openclaw/test/proxy-manager.test.ts
-
curl -L -o proxy-manager.test.ts https://huggingface.co/spaces/minhtudragon/headroom_3/resolve/88e38edf9a06c75ad12f39e1287f5c1d6059190b/plugins/openclaw/test/proxy-manager.test.ts
9.34 kB
| import { describe, it, expect, afterEach, vi } from "vitest"; | |
| import { | |
| ProxyManager, | |
| normalizeAndValidateProxyUrl, | |
| isLocalProxyUrl, | |
| probeHeadroomProxy, | |
| } from "../src/proxy-manager.js"; | |
| afterEach(() => { | |
| vi.restoreAllMocks(); | |
| }); | |
| /** Stub fetch with a sequence of health/retrieve probe outcomes. */ | |
| function stubProbeSuccess() { | |
| const mock = vi.fn() | |
| .mockResolvedValueOnce({ ok: true, status: 200 }) // /health | |
| .mockResolvedValueOnce({ ok: true, status: 200 }); // /v1/retrieve/stats | |
| vi.stubGlobal("fetch", mock); | |
| return mock; | |
| } | |
| function stubProbeNonHeadroom() { | |
| const mock = vi.fn() | |
| .mockResolvedValueOnce({ ok: true, status: 200 }) // /health OK | |
| .mockResolvedValueOnce({ ok: false, status: 404 }); // /v1/retrieve/stats 404 | |
| vi.stubGlobal("fetch", mock); | |
| return mock; | |
| } | |
| function stubProbeUnreachable() { | |
| const mock = vi.fn().mockRejectedValue(new Error("ECONNREFUSED")); | |
| vi.stubGlobal("fetch", mock); | |
| return mock; | |
| } | |
| describe("normalizeAndValidateProxyUrl", () => { | |
| it("accepts localhost origins", () => { | |
| expect(normalizeAndValidateProxyUrl("http://127.0.0.1:8787")).toBe("http://127.0.0.1:8787"); | |
| expect(normalizeAndValidateProxyUrl("http://localhost:8787")).toBe("http://localhost:8787"); | |
| }); | |
| it("accepts remote URLs", () => { | |
| expect(normalizeAndValidateProxyUrl("http://example.com:8787")).toBe("http://example.com:8787"); | |
| expect(normalizeAndValidateProxyUrl("https://headroom.example.com")).toBe("https://headroom.example.com"); | |
| expect(normalizeAndValidateProxyUrl("https://headroom.example.com:9090")).toBe("https://headroom.example.com:9090"); | |
| }); | |
| it("rejects malformed URLs", () => { | |
| expect(() => normalizeAndValidateProxyUrl("ftp://localhost:8787")).toThrow( | |
| /must use http/, | |
| ); | |
| expect(() => normalizeAndValidateProxyUrl("http://localhost:8787/path")).toThrow( | |
| /must not include a path/, | |
| ); | |
| }); | |
| }); | |
| describe("isLocalProxyUrl", () => { | |
| it("returns true for localhost addresses", () => { | |
| expect(isLocalProxyUrl("http://127.0.0.1:8787")).toBe(true); | |
| expect(isLocalProxyUrl("http://localhost:8787")).toBe(true); | |
| }); | |
| it("returns false for remote addresses", () => { | |
| expect(isLocalProxyUrl("http://example.com:8787")).toBe(false); | |
| expect(isLocalProxyUrl("https://headroom.example.com")).toBe(false); | |
| }); | |
| it("returns false for invalid URLs", () => { | |
| expect(isLocalProxyUrl("not-a-url")).toBe(false); | |
| }); | |
| }); | |
| describe("probeHeadroomProxy", () => { | |
| it("returns reachable+isHeadroom when both endpoints succeed", async () => { | |
| stubProbeSuccess(); | |
| const result = await probeHeadroomProxy("http://127.0.0.1:8787"); | |
| expect(result).toEqual({ reachable: true, isHeadroom: true }); | |
| }); | |
| it("returns reachable but non-headroom when retrieve endpoint fails", async () => { | |
| stubProbeNonHeadroom(); | |
| const result = await probeHeadroomProxy("http://127.0.0.1:8787"); | |
| expect(result.reachable).toBe(true); | |
| expect(result.isHeadroom).toBe(false); | |
| expect(result.reason).toMatch(/retrieve stats HTTP 404/); | |
| }); | |
| it("returns unreachable when health check fails", async () => { | |
| stubProbeUnreachable(); | |
| const result = await probeHeadroomProxy("http://127.0.0.1:8787"); | |
| expect(result.reachable).toBe(false); | |
| expect(result.isHeadroom).toBe(false); | |
| }); | |
| }); | |
| describe("ProxyManager.start", () => { | |
| it("auto-detects running proxy on default candidates", async () => { | |
| const manager = new ProxyManager({}); | |
| // Candidate 1: health fail. Candidate 2: health+retrieve succeed. | |
| const fetchMock = vi | |
| .fn() | |
| .mockRejectedValueOnce(new Error("down")) | |
| .mockResolvedValueOnce({ ok: true, status: 200 }) | |
| .mockResolvedValueOnce({ ok: true, status: 200 }); | |
| vi.stubGlobal("fetch", fetchMock); | |
| const startSpy = vi.spyOn(manager as any, "startHeadroomProxy"); | |
| const url = await manager.start(); | |
| expect(url).toBe("http://localhost:8787"); | |
| expect(startSpy).not.toHaveBeenCalled(); | |
| }); | |
| it("uses proxyPort for auto-detect candidates", async () => { | |
| const manager = new ProxyManager({ proxyPort: 9797, autoStart: false }); | |
| const fetchMock = vi.fn().mockRejectedValue(new Error("down")); | |
| vi.stubGlobal("fetch", fetchMock); | |
| await expect(manager.start()).rejects.toThrow(/127\.0\.0\.1:9797.*localhost:9797/); | |
| }); | |
| it("rejects invalid proxyPort", async () => { | |
| const manager = new ProxyManager({ proxyPort: 0 }); | |
| await expect(manager.start()).rejects.toThrow(/proxyPort must be an integer between 1 and 65535/); | |
| }); | |
| it("fails when explicit URL is reachable but not a headroom proxy", async () => { | |
| const manager = new ProxyManager({ proxyUrl: "http://127.0.0.1:8787" }); | |
| stubProbeNonHeadroom(); | |
| await expect(manager.start()).rejects.toThrow(/does not appear to be a Headroom proxy/); | |
| }); | |
| it("applies default proxyPort when explicit proxyUrl omits port", async () => { | |
| const manager = new ProxyManager({ proxyUrl: "http://127.0.0.1", autoStart: true }); | |
| const startSpy = vi.spyOn(manager as any, "startHeadroomProxy").mockResolvedValue(undefined); | |
| const fetchMock = vi | |
| .fn() | |
| .mockRejectedValueOnce(new Error("down")) | |
| .mockResolvedValueOnce({ ok: true, status: 200 }) | |
| .mockResolvedValueOnce({ ok: true, status: 200 }); | |
| vi.stubGlobal("fetch", fetchMock); | |
| const url = await manager.start(); | |
| expect(url).toBe("http://127.0.0.1:8787"); | |
| expect(startSpy).toHaveBeenCalledWith("http://127.0.0.1:8787", 8787); | |
| }); | |
| it("connects to remote proxy without auto-start", async () => { | |
| const manager = new ProxyManager({ proxyUrl: "http://headroom.remote.example:8787", autoStart: true }); | |
| const startSpy = vi.spyOn(manager as any, "startHeadroomProxy").mockResolvedValue(undefined); | |
| stubProbeSuccess(); | |
| const url = await manager.start(); | |
| expect(url).toBe("http://headroom.remote.example:8787"); | |
| expect(startSpy).not.toHaveBeenCalled(); | |
| }); | |
| it("does not apply proxyPort default to remote URLs", async () => { | |
| const manager = new ProxyManager({ proxyUrl: "https://headroom.remote.example", proxyPort: 9999 }); | |
| stubProbeSuccess(); | |
| const url = await manager.start(); | |
| expect(url).toBe("https://headroom.remote.example"); | |
| }); | |
| it("fails fast for unreachable remote proxy without attempting auto-start", async () => { | |
| const manager = new ProxyManager({ proxyUrl: "https://headroom.remote.example:8787", autoStart: true }); | |
| const startSpy = vi.spyOn(manager as any, "startHeadroomProxy").mockResolvedValue(undefined); | |
| stubProbeUnreachable(); | |
| await expect(manager.start()).rejects.toThrow(/Remote Headroom proxy not reachable/); | |
| expect(startSpy).not.toHaveBeenCalled(); | |
| }); | |
| it("auto-starts when nothing is detected", async () => { | |
| const manager = new ProxyManager({ autoStart: true }); | |
| const startSpy = vi.spyOn(manager as any, "startHeadroomProxy").mockResolvedValue(undefined); | |
| // First two candidate probes fail (health only), then waitForHealthy probe succeeds. | |
| const fetchMock = vi | |
| .fn() | |
| .mockRejectedValueOnce(new Error("down")) | |
| .mockRejectedValueOnce(new Error("down")) | |
| .mockResolvedValueOnce({ ok: true, status: 200 }) | |
| .mockResolvedValueOnce({ ok: true, status: 200 }); | |
| vi.stubGlobal("fetch", fetchMock); | |
| const url = await manager.start(); | |
| expect(url).toBe("http://127.0.0.1:8787"); | |
| expect(startSpy).toHaveBeenCalledWith("http://127.0.0.1:8787", 8787); | |
| }); | |
| }); | |
| describe("ProxyManager launch internals", () => { | |
| it("prefers configured pythonPath in fallback order", () => { | |
| const manager = new ProxyManager({ pythonPath: "C:\\Python311\\python.exe" }); | |
| const commands = (manager as any).getPythonCommands() as string[]; | |
| expect(commands[0]).toBe("C:\\Python311\\python.exe"); | |
| expect(commands).toContain("python"); | |
| expect(commands).toContain("python3"); | |
| expect(commands).toContain("py"); | |
| }); | |
| it("uses first available launcher from provided specs", async () => { | |
| const manager = new ProxyManager({}); | |
| (manager as any).buildLaunchSpecs = () => [ | |
| { | |
| label: "first", | |
| command: "first-missing-command", | |
| args: ["proxy"], | |
| checkCommand: "first-missing-command", | |
| checkArgs: ["--version"], | |
| }, | |
| { | |
| label: "second-node", | |
| command: "node", | |
| args: ["-e", ""], | |
| checkCommand: "node", | |
| checkArgs: ["--version"], | |
| }, | |
| ]; | |
| const infoSpy = vi.spyOn((manager as any).logger, "info"); | |
| await (manager as any).startHeadroomProxy("http://127.0.0.1:8787"); | |
| expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining("Auto-start launcher selected")); | |
| expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining("second-node")); | |
| }); | |
| it("throws when no launcher is executable", async () => { | |
| const manager = new ProxyManager({}); | |
| (manager as any).buildLaunchSpecs = () => [ | |
| { | |
| label: "none", | |
| command: "none", | |
| args: ["proxy"], | |
| checkCommand: "none", | |
| checkArgs: ["--version"], | |
| }, | |
| ]; | |
| (manager as any).canExecute = () => false; | |
| await expect((manager as any).startHeadroomProxy("http://127.0.0.1:8787")).rejects.toThrow( | |
| /No usable Headroom launcher found/, | |
| ); | |
| }); | |
| }); | |