diff --git a/extensions/codex/src/app-server/protocol-validators.test.ts b/extensions/codex/src/app-server/protocol-validators.test.ts index 615d357c70c..28985296e41 100644 --- a/extensions/codex/src/app-server/protocol-validators.test.ts +++ b/extensions/codex/src/app-server/protocol-validators.test.ts @@ -36,6 +36,21 @@ function makeMinimalResponse(threadOverrides: Record = {}) { }; } +describe("Codex thread response validators", () => { + it("normalizes missing sessionId from id for start and resume responses", () => { + for (const assertResponse of [ + assertCodexThreadStartResponse, + assertCodexThreadResumeResponse, + ]) { + const response = makeMinimalResponse({ sessionId: undefined }); + delete (response.thread as Record).sessionId; + const result = assertResponse(response); + expect(result.thread.id).toBe("thread-1"); + expect(result.thread.sessionId).toBe("thread-1"); + } + }); +}); + describe("assertCodexThreadStartResponse", () => { it("accepts response with both id and sessionId", () => { const response = makeMinimalResponse(); @@ -44,15 +59,6 @@ describe("assertCodexThreadStartResponse", () => { expect(result.thread.sessionId).toBe("session-1"); }); - it("normalizes missing sessionId from id", () => { - const response = makeMinimalResponse({ sessionId: undefined }); - // Remove the sessionId key entirely - delete (response.thread as Record).sessionId; - const result = assertCodexThreadStartResponse(response); - expect(result.thread.id).toBe("thread-1"); - expect(result.thread.sessionId).toBe("thread-1"); - }); - it("normalizes missing id from sessionId", () => { const response = makeMinimalResponse({ id: undefined, sessionId: "session-1" }); delete (response.thread as Record).id; @@ -66,16 +72,6 @@ describe("assertCodexThreadStartResponse", () => { }); }); -describe("assertCodexThreadResumeResponse", () => { - it("normalizes missing sessionId from id", () => { - const response = makeMinimalResponse({ sessionId: undefined }); - delete (response.thread as Record).sessionId; - const result = assertCodexThreadResumeResponse(response); - expect(result.thread.id).toBe("thread-1"); - expect(result.thread.sessionId).toBe("thread-1"); - }); -}); - describe("readCodexModelListResponse", () => { it("applies defaults from generated schemas behind local refs", () => { const response = readCodexModelListResponse({ diff --git a/extensions/codex/src/app-server/request.test.ts b/extensions/codex/src/app-server/request.test.ts index 41497747c8f..35678e275a2 100644 --- a/extensions/codex/src/app-server/request.test.ts +++ b/extensions/codex/src/app-server/request.test.ts @@ -35,16 +35,18 @@ describe("requestCodexAppServerJson sandbox guard", () => { }); it("fails closed before raw app-server bypass methods when exec host=node is active", async () => { - await expect( - requestCodexAppServerJson({ - method: "command/exec", - requestParams: { command: ["sh", "-lc", "id"] }, - config: { tools: { exec: { host: "node", node: "worker-1" } } }, - sessionKey: "node-session", - }), - ).rejects.toThrow( - "Codex-native app-server method `command/exec` is unavailable because OpenClaw exec host=node is active for this session.", - ); + for (const method of ["command/exec", "process/spawn"]) { + await expect( + requestCodexAppServerJson({ + method, + requestParams: { command: ["sh", "-lc", "id"] }, + config: { tools: { exec: { host: "node", node: "worker-1" } } }, + sessionKey: "node-session", + }), + ).rejects.toThrow( + `Codex-native app-server method \`${method}\` is unavailable because OpenClaw exec host=node is active for this session.`, + ); + } expect(sharedClientMocks.getSharedCodexAppServerClient).not.toHaveBeenCalled(); }); @@ -65,21 +67,6 @@ describe("requestCodexAppServerJson sandbox guard", () => { expect(request).toHaveBeenCalledWith("thread/list", { limit: 10 }, { timeoutMs: 60_000 }); }); - it("fails closed before raw app-server bypass methods when exec host=node is active", async () => { - await expect( - requestCodexAppServerJson({ - method: "process/spawn", - requestParams: { command: ["sh", "-lc", "id"] }, - config: { tools: { exec: { host: "node", node: "worker-1" } } }, - sessionKey: "node-session", - }), - ).rejects.toThrow( - "Codex-native app-server method `process/spawn` is unavailable because OpenClaw exec host=node is active for this session.", - ); - - expect(sharedClientMocks.getSharedCodexAppServerClient).not.toHaveBeenCalled(); - }); - it("fails closed for config-level exec host=node even without a session key", async () => { await expect( requestCodexAppServerJson({ diff --git a/extensions/deepinfra/surface-model-catalogs.test.ts b/extensions/deepinfra/surface-model-catalogs.test.ts index 628bc67ceaf..c40a982164a 100644 --- a/extensions/deepinfra/surface-model-catalogs.test.ts +++ b/extensions/deepinfra/surface-model-catalogs.test.ts @@ -48,10 +48,7 @@ const surfaceEntry = (id: string, surfaceTag: string, extra: Record, - run: () => Promise, -) { +async function withLiveFetch(mockFetch: ReturnType, run: () => Promise) { const env = { ...process.env }; delete process.env.NODE_ENV; delete process.env.VITEST; @@ -79,12 +76,14 @@ async function withLiveFetch( } } -describe("listDeepInfraImageGenCatalog", () => { - it("returns null when no discoveryApiKey is configured", async () => { - const result = await listDeepInfraImageGenCatalog(makeCtx()); - expect(result).toBeNull(); +describe("DeepInfra generation catalogs", () => { + it("return null when no discoveryApiKey is configured", async () => { + await expect(listDeepInfraImageGenCatalog(makeCtx())).resolves.toBeNull(); + await expect(listDeepInfraVideoGenCatalog(makeCtx())).resolves.toBeNull(); }); +}); +describe("listDeepInfraImageGenCatalog", () => { it("returns null when live discovery succeeds but the response has zero image-gen entries", async () => { const mockFetch = vi.fn().mockResolvedValue({ ok: true, @@ -155,11 +154,6 @@ describe("listDeepInfraImageGenCatalog", () => { }); describe("listDeepInfraVideoGenCatalog", () => { - it("returns null when no discoveryApiKey is configured", async () => { - const result = await listDeepInfraVideoGenCatalog(makeCtx()); - expect(result).toBeNull(); - }); - it("returns null when live discovery succeeds but the response has zero video-gen entries", async () => { // Current production state: TTS/STT/T2V models lack the OPENAI tag the // backend filter requires, so a key-authenticated discovery still @@ -208,10 +202,7 @@ describe("listDeepInfraVideoGenCatalog", () => { await withLiveFetch(mockFetch, async () => { const result = await listDeepInfraVideoGenCatalog(withKeyCtx()); expect(result).not.toBeNull(); - expect(result?.map((e) => e.model)).toEqual([ - "Wan-AI/Wan2.6-T2V", - "ByteDance/Seedance-2.0", - ]); + expect(result?.map((e) => e.model)).toEqual(["Wan-AI/Wan2.6-T2V", "ByteDance/Seedance-2.0"]); const first = result?.[0]; expect(first?.kind).toBe("video_generation"); expect(first?.capabilities?.generate?.supportsAspectRatio).toBe(true); diff --git a/extensions/signal/src/client-container.test.ts b/extensions/signal/src/client-container.test.ts index ad7e8052fbd..ff07d1409c2 100644 --- a/extensions/signal/src/client-container.test.ts +++ b/extensions/signal/src/client-container.test.ts @@ -846,6 +846,7 @@ describe("containerSendReaction", () => { emoji: "👍", targetAuthor: "+15550001111", targetTimestamp: 1699999999999, + groupId: "group-123", }); expect(result).toEqual({ timestamp: 1700000000000 }); @@ -856,30 +857,10 @@ describe("containerSendReaction", () => { reaction: "👍", target_author: "+15550001111", timestamp: 1699999999999, + group_id: "group-123", }), ); }); - - it("includes group_id when provided", async () => { - mockFetch.mockResolvedValue({ - ok: true, - status: 200, - text: async () => JSON.stringify({}), - }); - - await containerSendReaction({ - baseUrl: "http://localhost:8080", - account: "+14259798283", - recipient: "+15550001111", - emoji: "❤️", - targetAuthor: "+15550001111", - targetTimestamp: 1699999999999, - groupId: "group-123", - }); - - const body = parseFetchBody(); - expect(body.group_id).toBe("group-123"); - }); }); describe("containerRpcRequest reactions", () => { @@ -933,6 +914,7 @@ describe("containerRemoveReaction", () => { emoji: "👍", targetAuthor: "+15550001111", targetTimestamp: 1699999999999, + groupId: "group-123", }); expect(result).toEqual({ timestamp: 1700000000000 }); @@ -946,28 +928,8 @@ describe("containerRemoveReaction", () => { reaction: "👍", target_author: "+15550001111", timestamp: 1699999999999, + group_id: "group-123", }), ); }); - - it("includes group_id when provided", async () => { - mockFetch.mockResolvedValue({ - ok: true, - status: 200, - text: async () => JSON.stringify({}), - }); - - await containerRemoveReaction({ - baseUrl: "http://localhost:8080", - account: "+14259798283", - recipient: "+15550001111", - emoji: "❤️", - targetAuthor: "+15550001111", - targetTimestamp: 1699999999999, - groupId: "group-123", - }); - - const body = parseFetchBody(); - expect(body.group_id).toBe("group-123"); - }); }); diff --git a/src/agents/model-compat.test.ts b/src/agents/model-compat.test.ts index d330d99d1a3..b4b4abe09f9 100644 --- a/src/agents/model-compat.test.ts +++ b/src/agents/model-compat.test.ts @@ -149,58 +149,72 @@ describe("normalizeModelCompat — Anthropic baseUrl", () => { }); describe("normalizeModelCompat", () => { - it("forces supportsDeveloperRole off for z.ai models", () => { - expectSupportsDeveloperRoleForcedOff(); - }); + it.each([ + ["z.ai models", undefined], + ["moonshot models", { provider: "moonshot", baseUrl: "https://api.moonshot.ai/v1" }], + [ + "custom moonshot-compatible endpoints", + { provider: "custom-kimi", baseUrl: "https://api.moonshot.cn/v1" }, + ], + [ + "DashScope provider ids", + { provider: "dashscope", baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1" }, + ], + [ + "DashScope-compatible endpoints", + { + provider: "custom-qwen", + baseUrl: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", + }, + ], + [ + "Azure OpenAI chat completions", + { provider: "azure-openai", baseUrl: "https://my-deployment.openai.azure.com/openai" }, + ], + [ + "generic custom openai-completions providers", + { provider: "custom-cpa", baseUrl: "https://cpa.example.com/v1" }, + ], + [ + "Qwen proxy via openai-completions", + { provider: "qwen-proxy", baseUrl: "https://qwen-api.example.org/compatible-mode/v1" }, + ], + [ + "malformed baseUrl values", + { provider: "custom-cpa", baseUrl: "://api.openai.com malformed" }, + ], + ] satisfies Array<[string, Partial | undefined]>)( + "forces supportsDeveloperRole off for %s", + (_name, overrides) => { + expectSupportsDeveloperRoleForcedOff(overrides); + }, + ); - it("forces supportsDeveloperRole off for moonshot models", () => { - expectSupportsDeveloperRoleForcedOff({ - provider: "moonshot", - baseUrl: "https://api.moonshot.ai/v1", - }); - }); - - it("forces supportsDeveloperRole off for custom moonshot-compatible endpoints", () => { - expectSupportsDeveloperRoleForcedOff({ - provider: "custom-kimi", - baseUrl: "https://api.moonshot.cn/v1", - }); - }); - - it("forces supportsDeveloperRole off for DashScope provider ids", () => { - expectSupportsDeveloperRoleForcedOff({ - provider: "dashscope", - baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1", - }); - }); - - it("forces supportsDeveloperRole off for DashScope-compatible endpoints", () => { - expectSupportsDeveloperRoleForcedOff({ - provider: "custom-qwen", - baseUrl: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", - }); - }); - - it("keeps supportsUsageInStreaming on for native Qwen endpoints", () => { - expectNativeStreamingSupported({ - provider: "qwen", - baseUrl: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", - }); - }); - - it("keeps supportsUsageInStreaming on for DashScope-compatible endpoints regardless of provider id", () => { - expectNativeStreamingSupported({ - provider: "custom-qwen", - baseUrl: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", - }); - }); - - it("keeps supportsUsageInStreaming on for Moonshot-native endpoints regardless of provider id", () => { - expectNativeStreamingSupported({ - provider: "custom-kimi", - baseUrl: "https://api.moonshot.ai/v1", - }); - }); + it.each([ + [ + "native Qwen endpoints", + { + provider: "qwen", + baseUrl: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", + }, + ], + [ + "DashScope-compatible endpoints regardless of provider id", + { + provider: "custom-qwen", + baseUrl: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1", + }, + ], + [ + "Moonshot-native endpoints regardless of provider id", + { provider: "custom-kimi", baseUrl: "https://api.moonshot.ai/v1" }, + ], + ] satisfies Array<[string, Partial]>)( + "keeps supportsUsageInStreaming on for %s", + (_name, overrides) => { + expectNativeStreamingSupported(overrides); + }, + ); it("leaves native api.openai.com model untouched", () => { const model = { @@ -213,19 +227,6 @@ describe("normalizeModelCompat", () => { expect(normalized.compat).toBeUndefined(); }); - it("forces supportsDeveloperRole off for Azure OpenAI (Chat Completions, not Responses API)", () => { - expectSupportsDeveloperRoleForcedOff({ - provider: "azure-openai", - baseUrl: "https://my-deployment.openai.azure.com/openai", - }); - }); - it("forces supportsDeveloperRole off for generic custom openai-completions provider", () => { - expectSupportsDeveloperRoleForcedOff({ - provider: "custom-cpa", - baseUrl: "https://cpa.example.com/v1", - }); - }); - it("forces supportsUsageInStreaming off for generic custom openai-completions provider", () => { expectSupportsUsageInStreamingForcedOff({ provider: "custom-cpa", @@ -233,23 +234,18 @@ describe("normalizeModelCompat", () => { }); }); - it("forces supportsStrictMode off for z.ai models", () => { - expectSupportsStrictModeForcedOff(); - }); - - it("forces supportsStrictMode off for custom openai-completions provider", () => { - expectSupportsStrictModeForcedOff({ - provider: "custom-cpa", - baseUrl: "https://cpa.example.com/v1", - }); - }); - - it("forces supportsDeveloperRole off for Qwen proxy via openai-completions", () => { - expectSupportsDeveloperRoleForcedOff({ - provider: "qwen-proxy", - baseUrl: "https://qwen-api.example.org/compatible-mode/v1", - }); - }); + it.each([ + ["z.ai models", undefined], + [ + "custom openai-completions providers", + { provider: "custom-cpa", baseUrl: "https://cpa.example.com/v1" }, + ], + ] satisfies Array<[string, Partial | undefined]>)( + "forces supportsStrictMode off for %s", + (_name, overrides) => { + expectSupportsStrictModeForcedOff(overrides); + }, + ); it("leaves openai-completions model with empty baseUrl untouched", () => { const model = { @@ -262,13 +258,6 @@ describe("normalizeModelCompat", () => { expect(normalized.compat).toBeUndefined(); }); - it("forces supportsDeveloperRole off for malformed baseUrl values", () => { - expectSupportsDeveloperRoleForcedOff({ - provider: "custom-cpa", - baseUrl: "://api.openai.com malformed", - }); - }); - it("respects explicit supportsDeveloperRole true on non-native endpoints", () => { const model = { ...baseModel(), diff --git a/src/auto-reply/reply/commands-acp/context.test.ts b/src/auto-reply/reply/commands-acp/context.test.ts index 869fc89c18a..03c8893c6d9 100644 --- a/src/auto-reply/reply/commands-acp/context.test.ts +++ b/src/auto-reply/reply/commands-acp/context.test.ts @@ -702,24 +702,6 @@ describe("commands-acp context", () => { expect(resolveAcpCommandConversationId(params)).toBe("iMessage;+;chat123"); }); - it("resolves iMessage DM conversation ids from current targets", () => { - const params = buildCommandTestParams("/acp status", baseCfg, { - Provider: "imessage", - Surface: "imessage", - OriginatingChannel: "imessage", - OriginatingTo: "imessage:+15555550123", - }); - - expect(resolveAcpCommandBindingContext(params)).toEqual({ - channel: "imessage", - accountId: "default", - threadId: undefined, - conversationId: "+15555550123", - parentConversationId: undefined, - }); - expect(resolveAcpCommandConversationId(params)).toBe("+15555550123"); - }); - it("resolves iMessage group conversation ids from chat_id targets", () => { const params = buildCommandTestParams("/acp status", baseCfg, { Provider: "imessage", diff --git a/src/config/config-misc.test.ts b/src/config/config-misc.test.ts index 619df0991fc..f1d851f8071 100644 --- a/src/config/config-misc.test.ts +++ b/src/config/config-misc.test.ts @@ -161,34 +161,22 @@ describe("model provider localService config", () => { }); it("accepts bundled provider timeout overlays without custom provider fields", () => { - const result = validateConfigObjectRaw({ - models: { - providers: { - openai: { - timeoutSeconds: 600, + for (const provider of ["openai", "zai"] as const) { + const result = validateConfigObjectRaw({ + models: { + providers: { + [provider]: { + timeoutSeconds: 600, + }, }, }, - }, - }); + }); - expect(result.ok).toBe(true); - }); - - it("accepts bundled provider timeout overlays without custom provider fields", () => { - const result = validateConfigObjectRaw({ - models: { - providers: { - zai: { - timeoutSeconds: 600, - }, - }, - }, - }); - - expect(result.ok).toBe(true); - if (result.ok) { - expect(result.config.models?.providers?.zai?.models).toEqual([]); - expect(result.config.models?.providers?.zai?.baseUrl).toBe(""); + expect(result.ok).toBe(true); + if (provider === "zai" && result.ok) { + expect(result.config.models?.providers?.zai?.models).toEqual([]); + expect(result.config.models?.providers?.zai?.baseUrl).toBe(""); + } } }); diff --git a/src/config/sessions/transcript-stream.test.ts b/src/config/sessions/transcript-stream.test.ts index f112b537972..9afe76668a8 100644 --- a/src/config/sessions/transcript-stream.test.ts +++ b/src/config/sessions/transcript-stream.test.ts @@ -33,6 +33,15 @@ async function collect(iter: AsyncGenerator): Promise { return out; } +describe("transcript stream empty files", () => { + it("returns empty iterators for empty files in both directions", async () => { + fs.writeFileSync(transcriptPath, "", "utf-8"); + + await expect(collect(streamSessionTranscriptLines(transcriptPath))).resolves.toEqual([]); + await expect(collect(streamSessionTranscriptLinesReverse(transcriptPath))).resolves.toEqual([]); + }); +}); + describe("streamSessionTranscriptLines", () => { it("yields trimmed non-empty lines in file order", async () => { fs.writeFileSync(transcriptPath, " alpha \n\nbeta\n \r\ngamma\n", "utf-8"); @@ -48,14 +57,6 @@ describe("streamSessionTranscriptLines", () => { expect(lines).toEqual([]); }); - it("returns an empty iterator for an empty file", async () => { - fs.writeFileSync(transcriptPath, "", "utf-8"); - - const lines = await collect(streamSessionTranscriptLines(transcriptPath)); - - expect(lines).toEqual([]); - }); - it("forwards malformed JSON lines as raw text so callers can choose to skip them", async () => { fs.writeFileSync( transcriptPath, @@ -112,14 +113,6 @@ describe("streamSessionTranscriptLinesReverse", () => { expect(lines).toEqual([]); }); - it("returns an empty iterator for an empty file", async () => { - fs.writeFileSync(transcriptPath, "", "utf-8"); - - const lines = await collect(streamSessionTranscriptLinesReverse(transcriptPath)); - - expect(lines).toEqual([]); - }); - it("preserves complete lines across chunk boundaries", async () => { const longLine = "x".repeat(2048); fs.writeFileSync(transcriptPath, `${longLine}\nbeta\ngamma\n`, "utf-8"); diff --git a/src/gateway/gateway-models.profiles.live.test.ts b/src/gateway/gateway-models.profiles.live.test.ts index 26d088a4bc4..36f2cd43bad 100644 --- a/src/gateway/gateway-models.profiles.live.test.ts +++ b/src/gateway/gateway-models.profiles.live.test.ts @@ -746,18 +746,17 @@ describe("resolveGatewayLiveModelTimeoutMs", () => { it("defaults to the release live model budget", () => { expect(resolveGatewayLiveModelTimeoutMs("", undefined, 90_000)).toBe(300_000); }); - - it("never goes below the probe timeout", () => { - expect(resolveGatewayLiveModelTimeoutMs("45000", undefined, 90_000)).toBe(90_000); - }); }); describe("resolveGatewayLiveTranscriptTimeoutMs", () => { it("uses the model budget for transcript waits", () => { expect(resolveGatewayLiveTranscriptTimeoutMs(90_000, 180_000)).toBe(180_000); }); +}); +describe("gateway live timeout floors", () => { it("never goes below the probe timeout", () => { + expect(resolveGatewayLiveModelTimeoutMs("45000", undefined, 90_000)).toBe(90_000); expect(resolveGatewayLiveTranscriptTimeoutMs(240_000, 180_000)).toBe(240_000); }); }); diff --git a/src/gateway/tools-invoke-http.test.ts b/src/gateway/tools-invoke-http.test.ts index 675b176384b..85ed6967485 100644 --- a/src/gateway/tools-invoke-http.test.ts +++ b/src/gateway/tools-invoke-http.test.ts @@ -861,6 +861,26 @@ describe("POST /tools/invoke", () => { const body = await expectOkInvokeResponse(res); expect(body.result).toEqual({ ok: true, result: [] }); + + setMainAllowedTools({ allow: ["write_scoped_test"] }); + vi.mocked(authorizeHttpGatewayConnect).mockResolvedValueOnce({ + ok: true, + method: "token", + }); + + const writeScopedRes = await invokeTool({ + port: sharedPort, + headers: { + authorization: "Bearer secret", + "x-openclaw-scopes": "operator.approvals", + }, + tool: "write_scoped_test", + sessionKey: "main", + }); + + const writeScopedBody = await expectOkInvokeResponse(writeScopedRes); + expect(writeScopedBody.result).toEqual({ ok: true, result: "write-scoped" }); + expect(lastCreateOpenClawToolsContext?.senderIsOwner).toBe(true); }); it("executes tools for write-scoped callers on the HTTP path", async () => { @@ -899,28 +919,6 @@ describe("POST /tools/invoke", () => { expect(lastCreateOpenClawToolsContext?.senderIsOwner).toBe(true); }); - it("treats shared-secret bearer auth as full operator access on /tools/invoke", async () => { - setMainAllowedTools({ allow: ["write_scoped_test"] }); - vi.mocked(authorizeHttpGatewayConnect).mockResolvedValueOnce({ - ok: true, - method: "token", - }); - - const res = await invokeTool({ - port: sharedPort, - headers: { - authorization: "Bearer secret", - "x-openclaw-scopes": "operator.approvals", - }, - tool: "write_scoped_test", - sessionKey: "main", - }); - - const body = await expectOkInvokeResponse(res); - expect(body.result).toEqual({ ok: true, result: "write-scoped" }); - expect(lastCreateOpenClawToolsContext?.senderIsOwner).toBe(true); - }); - it("extends the HTTP deny list to high-risk execution and file tools", async () => { setMainAllowedTools({ allow: ["exec", "apply_patch", "nodes"] }); diff --git a/src/infra/outbound/envelope.test.ts b/src/infra/outbound/envelope.test.ts index f68ffe917b8..131fcb167a0 100644 --- a/src/infra/outbound/envelope.test.ts +++ b/src/infra/outbound/envelope.test.ts @@ -29,6 +29,25 @@ describe("buildOutboundResultEnvelope", () => { meta: { ok: true }, }, }, + { + input: { + payloads: [], + delivery, + meta: { delivered: true }, + }, + expected: { + payloads: [], + meta: { delivered: true }, + delivery, + }, + }, + { + input: { + delivery, + flattenDelivery: false, + }, + expected: { delivery }, + }, ])("formats outbound envelope for %j", ({ input, expected }) => { const envelope = buildOutboundResultEnvelope(input); expect(envelope).toEqual(expected); diff --git a/src/infra/outbound/outbound.test.ts b/src/infra/outbound/outbound.test.ts index c7e25a575e7..632884cd0fa 100644 --- a/src/infra/outbound/outbound.test.ts +++ b/src/infra/outbound/outbound.test.ts @@ -2,10 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../config/config.js"; import { setActivePluginRegistry } from "../../plugins/runtime.js"; import { createTestRegistry } from "../../test-utils/channel-plugins.js"; -import { typedCases } from "../../test-utils/typed-cases.js"; import { DirectoryCache } from "./directory-cache.js"; -import { buildOutboundResultEnvelope } from "./envelope.js"; -import type { OutboundDeliveryJson } from "./format.js"; beforeEach(() => { setActivePluginRegistry(createTestRegistry([])); @@ -58,70 +55,3 @@ describe("DirectoryCache", () => { expect(cache.get("c", cfg)).toBe(expected.c); }); }); - -describe("buildOutboundResultEnvelope", () => { - const directChatDelivery: OutboundDeliveryJson = { - channel: "directchat", - via: "gateway", - to: "+1", - messageId: "m1", - mediaUrl: null, - }; - const alphaDelivery: OutboundDeliveryJson = { - channel: "alpha", - via: "direct", - to: "123", - messageId: "m2", - mediaUrl: null, - chatId: "c1", - }; - const richChatDelivery: OutboundDeliveryJson = { - channel: "richchat", - via: "gateway", - to: "channel:C1", - messageId: "m3", - mediaUrl: null, - channelId: "C1", - }; - - it.each( - typedCases<{ - name: string; - input: Parameters[0]; - expected: unknown; - }>([ - { - name: "flatten delivery by default", - input: { delivery: directChatDelivery }, - expected: directChatDelivery, - }, - { - name: "keep payloads + meta", - input: { - payloads: [{ text: "hi", mediaUrl: null, mediaUrls: undefined }], - meta: { foo: "bar" }, - }, - expected: { - payloads: [{ text: "hi", mediaUrl: null, mediaUrls: undefined }], - meta: { foo: "bar" }, - }, - }, - { - name: "include delivery when payloads exist", - input: { payloads: [], delivery: alphaDelivery, meta: { ok: true } }, - expected: { - payloads: [], - meta: { ok: true }, - delivery: alphaDelivery, - }, - }, - { - name: "keep wrapped delivery when flatten disabled", - input: { delivery: richChatDelivery, flattenDelivery: false }, - expected: { delivery: richChatDelivery }, - }, - ]), - )("$name", ({ input, expected }) => { - expect(buildOutboundResultEnvelope(input)).toEqual(expected); - }); -}); diff --git a/src/plugin-sdk/browser-facades.test.ts b/src/plugin-sdk/browser-facades.test.ts index 6df2119da0f..ca1201e6bcb 100644 --- a/src/plugin-sdk/browser-facades.test.ts +++ b/src/plugin-sdk/browser-facades.test.ts @@ -1,9 +1,4 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; -import { - expectBrowserHostInspectionDelegation, - expectBrowserHostInspectionFacadeUnavailable, - mockBrowserHostInspectionFacade, -} from "./browser-facade-test-helpers.js"; const loadBundledPluginPublicSurfaceModuleSync = vi.hoisted(() => vi.fn()); @@ -104,24 +99,4 @@ describe("plugin-sdk browser facades", () => { "missing browser control auth facade", ); }); - - it("delegates browser host inspection helpers to the browser facade", async () => { - const executable: import("./browser-host-inspection.js").BrowserExecutable = { - kind: "chrome", - path: "/usr/bin/google-chrome", - }; - mockBrowserHostInspectionFacade(loadBundledPluginPublicSurfaceModuleSync, executable); - - const hostInspection = await import("./browser-host-inspection.js"); - - expectBrowserHostInspectionDelegation({ - executable, - hostInspection, - loadBundledPluginPublicSurfaceModuleSync, - }); - }); - - it("hard-fails when browser host inspection facade is unavailable", async () => { - await expectBrowserHostInspectionFacadeUnavailable(loadBundledPluginPublicSurfaceModuleSync); - }); }); diff --git a/src/plugins/discovery-threading.test.ts b/src/plugins/discovery-threading.test.ts index 15d06741a39..1674756dd1f 100644 --- a/src/plugins/discovery-threading.test.ts +++ b/src/plugins/discovery-threading.test.ts @@ -23,41 +23,34 @@ describe("discovery threading", () => { discoverOpenClawPluginsMock.mockReturnValue(emptyDiscovery); }); - describe("loadPluginManifestRegistry", () => { - it("skips internal discoverOpenClawPlugins when discovery is supplied", () => { - loadPluginManifestRegistry({ discovery: emptyDiscovery }); - expect(discoverOpenClawPluginsMock).not.toHaveBeenCalled(); - }); + it("skips internal discoverOpenClawPlugins when discovery is supplied", () => { + loadPluginManifestRegistry({ discovery: emptyDiscovery }); + expect(discoverOpenClawPluginsMock).not.toHaveBeenCalled(); - it("calls discoverOpenClawPlugins when neither discovery nor candidates supplied", () => { - loadPluginManifestRegistry({}); - expect(discoverOpenClawPluginsMock).toHaveBeenCalledTimes(1); - }); - - it("prefers explicit candidates over discovery when both are supplied", () => { - loadPluginManifestRegistry({ candidates: [], diagnostics: [], discovery: emptyDiscovery }); - expect(discoverOpenClawPluginsMock).not.toHaveBeenCalled(); - }); + discoverOpenClawPluginsMock.mockClear(); + resolveInstalledPluginIndexRegistry({ discovery: emptyDiscovery, installRecords: {} }); + expect(discoverOpenClawPluginsMock).not.toHaveBeenCalled(); }); - describe("resolveInstalledPluginIndexRegistry", () => { - it("skips internal discoverOpenClawPlugins when discovery is supplied", () => { - resolveInstalledPluginIndexRegistry({ discovery: emptyDiscovery, installRecords: {} }); - expect(discoverOpenClawPluginsMock).not.toHaveBeenCalled(); - }); + it("calls discoverOpenClawPlugins when neither discovery nor candidates supplied", () => { + loadPluginManifestRegistry({}); + expect(discoverOpenClawPluginsMock).toHaveBeenCalledTimes(1); - it("calls discoverOpenClawPlugins when neither discovery nor candidates supplied", () => { - resolveInstalledPluginIndexRegistry({ installRecords: {} }); - expect(discoverOpenClawPluginsMock).toHaveBeenCalledTimes(1); - }); + discoverOpenClawPluginsMock.mockClear(); + resolveInstalledPluginIndexRegistry({ installRecords: {} }); + expect(discoverOpenClawPluginsMock).toHaveBeenCalledTimes(1); + }); - it("prefers explicit candidates over discovery when both are supplied", () => { - resolveInstalledPluginIndexRegistry({ - candidates: [], - discovery: emptyDiscovery, - installRecords: {}, - }); - expect(discoverOpenClawPluginsMock).not.toHaveBeenCalled(); + it("prefers explicit candidates over discovery when both are supplied", () => { + loadPluginManifestRegistry({ candidates: [], diagnostics: [], discovery: emptyDiscovery }); + expect(discoverOpenClawPluginsMock).not.toHaveBeenCalled(); + + discoverOpenClawPluginsMock.mockClear(); + resolveInstalledPluginIndexRegistry({ + candidates: [], + discovery: emptyDiscovery, + installRecords: {}, }); + expect(discoverOpenClawPluginsMock).not.toHaveBeenCalled(); }); }); diff --git a/src/utils/timer-delay.test.ts b/src/utils/timer-delay.test.ts index 617ef7a5cdc..086b3e9fad1 100644 --- a/src/utils/timer-delay.test.ts +++ b/src/utils/timer-delay.test.ts @@ -1,41 +1,5 @@ import { describe, expect, it, vi } from "vitest"; -import { - MAX_SAFE_TIMEOUT_DELAY_MS, - resolveFiniteTimeoutDelayMs, - resolveSafeTimeoutDelayMs, - setSafeTimeout, -} from "./timer-delay.js"; - -describe("resolveSafeTimeoutDelayMs", () => { - it("clamps to Node's signed-32-bit timer ceiling", () => { - expect(resolveSafeTimeoutDelayMs(3_000_000_000)).toBe(MAX_SAFE_TIMEOUT_DELAY_MS); - }); - - it("respects custom minimums", () => { - expect(resolveSafeTimeoutDelayMs(10, { minMs: 250 })).toBe(250); - expect(resolveSafeTimeoutDelayMs(10, { minMs: 0 })).toBe(10); - }); - - it("falls back to the minimum for non-finite input", () => { - expect(resolveSafeTimeoutDelayMs(Number.POSITIVE_INFINITY, { minMs: 250 })).toBe(250); - expect(resolveSafeTimeoutDelayMs(Number.NaN)).toBe(1); - }); -}); - -describe("resolveFiniteTimeoutDelayMs", () => { - it("uses the fallback for missing or non-finite overrides", () => { - expect(resolveFiniteTimeoutDelayMs(undefined, 10_000, { minMs: 0 })).toBe(10_000); - expect(resolveFiniteTimeoutDelayMs(Number.NaN, 10_000, { minMs: 0 })).toBe(10_000); - expect(resolveFiniteTimeoutDelayMs(Number.POSITIVE_INFINITY, 10_000, { minMs: 0 })).toBe( - 10_000, - ); - }); - - it("still clamps finite overrides through safe timer bounds", () => { - expect(resolveFiniteTimeoutDelayMs(3_000_000_000, 10_000)).toBe(MAX_SAFE_TIMEOUT_DELAY_MS); - expect(resolveFiniteTimeoutDelayMs(-5, 10_000, { minMs: 0 })).toBe(0); - }); -}); +import { MAX_SAFE_TIMEOUT_DELAY_MS, setSafeTimeout } from "./timer-delay.js"; describe("setSafeTimeout", () => { it("arms setTimeout with the clamped delay", () => { diff --git a/test/openclaw-npm-release-check.test.ts b/test/openclaw-npm-release-check.test.ts index 1a737026ed5..694341263fa 100644 --- a/test/openclaw-npm-release-check.test.ts +++ b/test/openclaw-npm-release-check.test.ts @@ -456,28 +456,18 @@ describe("runNpmReleaseCheckCommand", () => { }); describe("resolveNpmReleaseCheckCommandTimeoutMs", () => { - it("uses a positive environment timeout", () => { - expect( - resolveNpmReleaseCheckCommandTimeoutMs({ - OPENCLAW_NPM_RELEASE_CHECK_COMMAND_TIMEOUT_MS: "1234", - }), - ).toBe(1234); - }); - - it("falls back when the environment timeout is invalid", () => { - expect( - resolveNpmReleaseCheckCommandTimeoutMs({ - OPENCLAW_NPM_RELEASE_CHECK_COMMAND_TIMEOUT_MS: "nope", - }), - ).toBe(10 * 60 * 1000); - }); - - it("falls back when the environment timeout has a numeric prefix", () => { - expect( - resolveNpmReleaseCheckCommandTimeoutMs({ - OPENCLAW_NPM_RELEASE_CHECK_COMMAND_TIMEOUT_MS: "10m", - }), - ).toBe(10 * 60 * 1000); + it("parses only positive integer environment timeouts", () => { + for (const [raw, expected] of [ + ["1234", 1234], + ["nope", 10 * 60 * 1000], + ["10m", 10 * 60 * 1000], + ] as const) { + expect( + resolveNpmReleaseCheckCommandTimeoutMs({ + OPENCLAW_NPM_RELEASE_CHECK_COMMAND_TIMEOUT_MS: raw, + }), + ).toBe(expected); + } }); }); diff --git a/test/openclaw-prepack.test.ts b/test/openclaw-prepack.test.ts index 8ca79dc6cae..f60519f2d6e 100644 --- a/test/openclaw-prepack.test.ts +++ b/test/openclaw-prepack.test.ts @@ -53,21 +53,15 @@ describe("runPrepackCommand", () => { }); describe("resolvePrepackCommandTimeoutMs", () => { - it("uses a positive environment timeout", () => { - expect(resolvePrepackCommandTimeoutMs({ OPENCLAW_PREPACK_COMMAND_TIMEOUT_MS: "1234" })).toBe( - 1234, - ); - }); - - it("falls back when the environment timeout is invalid", () => { - expect(resolvePrepackCommandTimeoutMs({ OPENCLAW_PREPACK_COMMAND_TIMEOUT_MS: "nope" })).toBe( - 30 * 60 * 1000, - ); - }); - - it("falls back when the environment timeout has a numeric prefix", () => { - expect(resolvePrepackCommandTimeoutMs({ OPENCLAW_PREPACK_COMMAND_TIMEOUT_MS: "10m" })).toBe( - 30 * 60 * 1000, - ); + it("parses only positive integer environment timeouts", () => { + for (const [raw, expected] of [ + ["1234", 1234], + ["nope", 30 * 60 * 1000], + ["10m", 30 * 60 * 1000], + ] as const) { + expect(resolvePrepackCommandTimeoutMs({ OPENCLAW_PREPACK_COMMAND_TIMEOUT_MS: raw })).toBe( + expected, + ); + } }); }); diff --git a/test/scripts/ensure-cli-startup-build.test.ts b/test/scripts/ensure-cli-startup-build.test.ts index 40166061a89..6c71d2a64f9 100644 --- a/test/scripts/ensure-cli-startup-build.test.ts +++ b/test/scripts/ensure-cli-startup-build.test.ts @@ -167,21 +167,15 @@ describe("ensure-cli-startup-build", () => { }); describe("resolveCliStartupBuildTimeoutMs", () => { - it("uses a positive environment timeout", () => { - expect(resolveCliStartupBuildTimeoutMs({ OPENCLAW_CLI_STARTUP_BUILD_TIMEOUT_MS: "4321" })).toBe( - 4321, - ); - }); - - it("falls back when the environment timeout is invalid", () => { - expect(resolveCliStartupBuildTimeoutMs({ OPENCLAW_CLI_STARTUP_BUILD_TIMEOUT_MS: "nope" })).toBe( - 10 * 60 * 1000, - ); - }); - - it("falls back when the environment timeout has a numeric prefix", () => { - expect(resolveCliStartupBuildTimeoutMs({ OPENCLAW_CLI_STARTUP_BUILD_TIMEOUT_MS: "10m" })).toBe( - 10 * 60 * 1000, - ); + it("parses only positive integer environment timeouts", () => { + for (const [raw, expected] of [ + ["4321", 4321], + ["nope", 10 * 60 * 1000], + ["10m", 10 * 60 * 1000], + ] as const) { + expect(resolveCliStartupBuildTimeoutMs({ OPENCLAW_CLI_STARTUP_BUILD_TIMEOUT_MS: raw })).toBe( + expected, + ); + } }); }); diff --git a/test/scripts/ensure-extension-memory-build.test.ts b/test/scripts/ensure-extension-memory-build.test.ts index 136c5e56524..f9545e87768 100644 --- a/test/scripts/ensure-extension-memory-build.test.ts +++ b/test/scripts/ensure-extension-memory-build.test.ts @@ -144,27 +144,17 @@ describe("ensure-extension-memory-build", () => { }); describe("resolveExtensionMemoryBuildTimeoutMs", () => { - it("uses a positive environment timeout", () => { - expect( - resolveExtensionMemoryBuildTimeoutMs({ - OPENCLAW_EXTENSION_MEMORY_BUILD_TIMEOUT_MS: "4321", - }), - ).toBe(4321); - }); - - it("falls back when the environment timeout is invalid", () => { - expect( - resolveExtensionMemoryBuildTimeoutMs({ - OPENCLAW_EXTENSION_MEMORY_BUILD_TIMEOUT_MS: "nope", - }), - ).toBe(10 * 60 * 1000); - }); - - it("falls back when the environment timeout has a numeric prefix", () => { - expect( - resolveExtensionMemoryBuildTimeoutMs({ - OPENCLAW_EXTENSION_MEMORY_BUILD_TIMEOUT_MS: "10m", - }), - ).toBe(10 * 60 * 1000); + it("parses only positive integer environment timeouts", () => { + for (const [raw, expected] of [ + ["4321", 4321], + ["nope", 10 * 60 * 1000], + ["10m", 10 * 60 * 1000], + ] as const) { + expect( + resolveExtensionMemoryBuildTimeoutMs({ + OPENCLAW_EXTENSION_MEMORY_BUILD_TIMEOUT_MS: raw, + }), + ).toBe(expected); + } }); });