






















11// Covers OpenAI-compatible embedding provider plugin behavior.
22import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
3-import type { AddressInfo } from "node:net";
3+import type { AddressInfo, Socket } from "node:net";
44import { afterEach, describe, expect, it } from "vitest";
55import { withEnvAsync } from "../test-utils/env.js";
66import type { EmbeddingProviderCreateOptions } from "./embedding-providers.js";
@@ -116,6 +116,53 @@ async function startEmbeddingServer(params?: {
116116};
117117}
118118119+async function startHangingErrorEmbeddingServer(): Promise<{
120+baseUrl: string;
121+closed: Promise<void>;
122+}> {
123+const sockets = new Set<Socket>();
124+let resolveClosed: () => void = () => undefined;
125+const closed = new Promise<void>((resolve) => {
126+resolveClosed = resolve;
127+});
128+const server = createServer((req: IncomingMessage, res: ServerResponse) => {
129+void (async () => {
130+await readJsonBody(req);
131+res.on("close", resolveClosed);
132+res.writeHead(502, { "content-type": "text/plain" });
133+res.write("x".repeat(12_000));
134+})();
135+});
136+server.on("connection", (socket) => {
137+sockets.add(socket);
138+socket.on("close", () => sockets.delete(socket));
139+});
140+141+await new Promise<void>((resolve, reject) => {
142+server.once("error", reject);
143+server.listen(0, "127.0.0.1", () => {
144+server.off("error", reject);
145+resolve();
146+});
147+});
148+149+servers.push({
150+close: () =>
151+new Promise<void>((resolve, reject) => {
152+for (const socket of sockets) {
153+socket.destroy();
154+}
155+server.close((error) => (error ? reject(error) : resolve()));
156+}),
157+});
158+159+const address = server.address() as AddressInfo;
160+return {
161+baseUrl: `http://127.0.0.1:${address.port}/v1`,
162+ closed,
163+};
164+}
165+119166afterEach(async () => {
120167const pending = servers.splice(0);
121168await Promise.all(pending.map((server) => server.close()));
@@ -250,6 +297,42 @@ describe("openai-compatible generic embedding provider", () => {
250297});
251298});
252299300+it("bounds and cancels non-ok embedding error bodies", async () => {
301+const server = await startHangingErrorEmbeddingServer();
302+const { provider } = await createOpenAICompatibleEmbeddingProvider(
303+createOptions({
304+model: "text-embedding-bge-m3",
305+remote: { baseUrl: server.baseUrl },
306+}),
307+);
308+309+const outcome = await Promise.race([
310+provider.embed("hello").then(
311+() => ({ type: "resolved" as const }),
312+(error: unknown) => ({ type: "rejected" as const, error }),
313+),
314+new Promise<{ type: "timed-out" }>((resolve) => {
315+setTimeout(() => resolve({ type: "timed-out" }), 1_000);
316+}),
317+]);
318+319+if (outcome.type !== "rejected") {
320+throw new Error(`expected embedding request to reject, got ${outcome.type}`);
321+}
322+expect(outcome.error).toBeInstanceOf(Error);
323+expect((outcome.error as Error).message).toBe(
324+`openai-compatible embeddings failed: HTTP 502: ${"x".repeat(1_000)}... [truncated]`,
325+);
326+await expect(
327+Promise.race([
328+server.closed.then(() => "closed" as const),
329+new Promise<"open">((resolve) => {
330+setTimeout(() => resolve("open"), 1_000);
331+}),
332+]),
333+).resolves.toBe("closed");
334+});
335+253336it("resolves env SecretRef API keys on the memory search secret surface", async () => {
254337const token = "env-secret-token";
255338const envVar = "OPENCLAW_TEST_OPENAI_COMPATIBLE_EMBEDDING_API_KEY";
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。