From edfa074e0f45092b776b38eb0c32af68a7ce6065 Mon Sep 17 00:00:00 2001 From: Mason Huang Date: Wed, 15 Apr 2026 18:31:23 +0800 Subject: [PATCH] Tests: align pnpm test expectations with main (#67001) Merged via squash. Prepared head SHA: 29c80680539131e532596c7944f64066b82ea307 Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com> Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com> Reviewed-by: @hxy91819 --- CHANGELOG.md | 1 + extensions/qa-channel/src/bus-client.test.ts | 80 ++++++++++++++++++ extensions/qa-channel/src/bus-client.ts | 83 +++++++++++++++---- extensions/qa-channel/src/channel.test.ts | 24 +++++- extensions/qa-lab/src/bus-server.ts | 10 ++- extensions/qa-lab/src/bus-state.test.ts | 44 ++++++++++ extensions/qa-lab/src/bus-state.ts | 8 ++ extensions/qa-lab/src/bus-waiters.ts | 50 ++++++++++- .../qa-lab/src/multipass.runtime.test.ts | 1 + extensions/qa-lab/src/multipass.runtime.ts | 5 +- ...aliases-schemas-without-dropping-b.test.ts | 11 ++- src/agents/skills-install-fallback.test.ts | 8 +- .../server-methods/server-methods.test.ts | 2 +- src/plugins/install.test.ts | 4 +- .../write-cli-startup-metadata.test.ts | 14 +--- 15 files changed, 301 insertions(+), 44 deletions(-) create mode 100644 extensions/qa-channel/src/bus-client.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 9747d327fc0..c820e8c1616 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai - QA/Matrix: split Matrix live QA into a source-linked `qa-matrix` runner and keep repo-private `qa-*` surfaces out of packaged and published builds. (#66723) Thanks @gumadeiras. - Control UI/Overview: add a Model Auth status card showing OAuth token health and provider rate-limit pressure at a glance, with attention callouts when OAuth tokens are expiring or expired. Backed by a new `models.authStatus` gateway method that strips credentials and caches for 60s. (#66211) Thanks @omarshahine. - GitHub Copilot/memory search: add a GitHub Copilot embedding provider for memory search, and expose a dedicated Copilot embedding host helper so plugins can reuse the transport while honoring remote overrides, token refresh, and safer payload validation. (#61718) Thanks @feiskyer and @vincentkoc. +- Tests: align pnpm test expectations with main (#67001). Thanks @hxy91819 ### Fixes diff --git a/extensions/qa-channel/src/bus-client.test.ts b/extensions/qa-channel/src/bus-client.test.ts new file mode 100644 index 00000000000..899cbb50e95 --- /dev/null +++ b/extensions/qa-channel/src/bus-client.test.ts @@ -0,0 +1,80 @@ +import { createServer } from "node:http"; +import { afterEach, describe, expect, it } from "vitest"; +import { getQaBusState, pollQaBus } from "./bus-client.js"; + +async function startJsonServer( + handler: (req: { url?: string | undefined }) => { statusCode?: number; body: string }, +) { + const server = createServer((req, res) => { + const response = handler({ url: req.url }); + res.writeHead(response.statusCode ?? 200, { + "content-type": "application/json; charset=utf-8", + }); + res.end(response.body); + }); + + await new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", () => resolve()); + }); + + const address = server.address(); + if (!address || typeof address === "string") { + throw new Error("test server failed to bind"); + } + + return { + baseUrl: `http://127.0.0.1:${address.port}`, + async stop() { + await new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())); + }); + }, + }; +} + +describe("qa-bus client", () => { + const stops: Array<() => Promise> = []; + + afterEach(async () => { + await Promise.all(stops.splice(0).map((stop) => stop())); + }); + + it("rejects malformed JSON responses instead of throwing from the stream callback", async () => { + const server = await startJsonServer(() => ({ + body: '{"cursor":1,"events":[', + })); + stops.push(server.stop); + + await expect( + pollQaBus({ + baseUrl: server.baseUrl, + accountId: "acct-a", + cursor: 0, + timeoutMs: 0, + }), + ).rejects.toThrow(SyntaxError); + }); + + it("preserves baseUrl path prefixes when composing bus URLs", async () => { + const server = await startJsonServer((req) => ({ + statusCode: req.url === "/qa-bus/v1/state" ? 200 : 404, + body: + req.url === "/qa-bus/v1/state" + ? JSON.stringify({ + cursor: 1, + conversations: [], + threads: [], + messages: [], + events: [], + }) + : JSON.stringify({ error: `unexpected path: ${req.url}` }), + })); + stops.push(server.stop); + + await expect(getQaBusState(`${server.baseUrl}/qa-bus`)).resolves.toMatchObject({ + cursor: 1, + events: [], + }); + }); +}); diff --git a/extensions/qa-channel/src/bus-client.ts b/extensions/qa-channel/src/bus-client.ts index bd79351962e..77bd143d09a 100644 --- a/extensions/qa-channel/src/bus-client.ts +++ b/extensions/qa-channel/src/bus-client.ts @@ -1,3 +1,5 @@ +import http from "node:http"; +import https from "node:https"; import type { QaBusConversation, QaBusEvent, @@ -32,27 +34,78 @@ export type { type JsonResult = Promise; +function buildQaBusUrl(baseUrl: string, path: string): URL { + const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`; + return new URL(path.replace(/^\/+/, ""), normalizedBaseUrl); +} + async function postJson( baseUrl: string, path: string, body: unknown, signal?: AbortSignal, ): JsonResult { - const response = await fetch(`${baseUrl}${path}`, { - method: "POST", - headers: { - "content-type": "application/json", - }, - body: JSON.stringify(body), - signal, + const url = buildQaBusUrl(baseUrl, path); + const payload = JSON.stringify(body); + const client = url.protocol === "https:" ? https : http; + + return await new Promise((resolve, reject) => { + const abortError = () => + Object.assign(new Error("The operation was aborted"), { name: "AbortError" }); + if (signal?.aborted) { + reject(abortError()); + return; + } + + const request = client.request( + url, + { + method: "POST", + headers: { + "content-type": "application/json", + "content-length": Buffer.byteLength(payload), + connection: "close", + }, + }, + (response) => { + const chunks: Buffer[] = []; + response.on("data", (chunk) => { + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + }); + response.on("end", () => { + const text = Buffer.concat(chunks).toString("utf8"); + let parsed: T | { error?: string }; + try { + parsed = text ? (JSON.parse(text) as T | { error?: string }) : ({} as T); + } catch (error) { + reject(error); + return; + } + if ((response.statusCode ?? 500) < 200 || (response.statusCode ?? 500) >= 300) { + const error = + typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : undefined; + reject(new Error(error || `qa-bus request failed: ${response.statusCode ?? 500}`)); + return; + } + resolve(parsed as T); + }); + response.on("error", reject); + }, + ); + + const onAbort = () => { + request.destroy(abortError()); + }; + signal?.addEventListener("abort", onAbort, { once: true }); + request.on("error", (error) => { + signal?.removeEventListener("abort", onAbort); + reject(error); + }); + request.on("close", () => { + signal?.removeEventListener("abort", onAbort); + }); + request.end(payload); }); - const payload = (await response.json()) as T | { error?: string }; - if (!response.ok) { - const error = - typeof payload === "object" && payload && "error" in payload ? payload.error : undefined; - throw new Error(error || `qa-bus request failed: ${response.status}`); - } - return payload as T; } export function normalizeQaTarget(raw: string): string | undefined { @@ -218,7 +271,7 @@ export async function injectQaBusInboundMessage(params: { } export async function getQaBusState(baseUrl: string): Promise { - const response = await fetch(`${baseUrl}/v1/state`); + const response = await fetch(buildQaBusUrl(baseUrl, "/v1/state")); if (!response.ok) { throw new Error(`qa-bus request failed: ${response.status}`); } diff --git a/extensions/qa-channel/src/channel.test.ts b/extensions/qa-channel/src/channel.test.ts index e72f7ed8d6c..1471b01a8d6 100644 --- a/extensions/qa-channel/src/channel.test.ts +++ b/extensions/qa-channel/src/channel.test.ts @@ -1,10 +1,24 @@ import type { PluginRuntime } from "openclaw/plugin-sdk/core"; -import { describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it } from "vitest"; import { extractToolPayload } from "../../../src/infra/outbound/tool-payload.js"; +import { + resetPluginRuntimeStateForTest, + setActivePluginRegistry, +} from "../../../src/plugins/runtime.js"; +import { createTestRegistry } from "../../../src/test-utils/channel-plugins.js"; import { createStartAccountContext } from "../../../test/helpers/plugins/start-account-context.js"; import { createQaBusState, startQaBusServer } from "../../qa-lab/api.js"; -import { qaChannelPlugin } from "../api.js"; -import { setQaChannelRuntime } from "../api.js"; +import { qaChannelPlugin, setQaChannelRuntime } from "../api.js"; + +afterEach(() => { + resetPluginRuntimeStateForTest(); +}); + +function installQaChannelTestRegistry() { + setActivePluginRegistry( + createTestRegistry([{ pluginId: "qa-channel", plugin: qaChannelPlugin, source: "test" }]), + ); +} function createMockQaRuntime(params?: { onDispatch?: (ctx: Record) => void; @@ -71,6 +85,7 @@ function createMockQaRuntime(params?: { describe("qa-channel plugin", () => { it("roundtrips inbound DM traffic through the qa bus", { timeout: 20_000 }, async () => { + installQaChannelTestRegistry(); const state = createQaBusState(); const bus = await startQaBusServer({ state }); setQaChannelRuntime(createMockQaRuntime()); @@ -120,6 +135,7 @@ describe("qa-channel plugin", () => { }); it("stages inbound image attachments into agent media payload", { timeout: 20_000 }, async () => { + installQaChannelTestRegistry(); const state = createQaBusState(); const bus = await startQaBusServer({ state }); let dispatchedCtx: Record | null = null; @@ -200,6 +216,7 @@ describe("qa-channel plugin", () => { }); it("exposes thread and message actions against the qa bus", async () => { + installQaChannelTestRegistry(); const state = createQaBusState(); const bus = await startQaBusServer({ state }); @@ -306,6 +323,7 @@ describe("qa-channel plugin", () => { }); it("routes the advertised send action to the qa bus", async () => { + installQaChannelTestRegistry(); const state = createQaBusState(); const bus = await startQaBusServer({ state }); diff --git a/extensions/qa-lab/src/bus-server.ts b/extensions/qa-lab/src/bus-server.ts index f5bc37ca459..5db53d45206 100644 --- a/extensions/qa-lab/src/bus-server.ts +++ b/extensions/qa-lab/src/bus-server.ts @@ -1,5 +1,6 @@ import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http"; import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; +import { normalizeAccountId } from "./bus-queries.js"; import type { QaBusState } from "./bus-state.js"; import type { QaBusCreateThreadInput, @@ -134,16 +135,17 @@ export async function handleQaBusRequest(params: { case "/v1/poll": { const input = body as unknown as QaBusPollInput; const timeoutMs = Math.max(0, Math.min(input.timeoutMs ?? 0, 30_000)); + const accountId = normalizeAccountId(input.accountId); const initial = params.state.poll(input); if (initial.events.length > 0 || timeoutMs === 0) { writeJson(params.res, 200, initial); return true; } try { - await params.state.waitFor({ - kind: "event-kind", - eventKind: "inbound-message", - timeoutMs, + await params.state.waitForCursorAdvance(input.cursor ?? 0, timeoutMs, (snapshot) => { + return snapshot.events.some( + (event) => event.accountId === accountId && event.cursor > (input.cursor ?? 0), + ); }); } catch { // timeout ok for long-poll diff --git a/extensions/qa-lab/src/bus-state.test.ts b/extensions/qa-lab/src/bus-state.test.ts index 0060cc350d0..9eb7d9549bb 100644 --- a/extensions/qa-lab/src/bus-state.test.ts +++ b/extensions/qa-lab/src/bus-state.test.ts @@ -92,6 +92,50 @@ describe("qa-bus state", () => { ).rejects.toThrow("qa-bus wait timeout"); }); + it("keeps account-scoped cursor waits blocked on unrelated account traffic", async () => { + const state = createQaBusState(); + const pending = state.waitForCursorAdvance(0, 500, (snapshot) => { + return snapshot.events.some((event) => event.accountId === "acct-a" && event.cursor > 0); + }); + + state.addInboundMessage({ + accountId: "acct-b", + conversation: { id: "other", kind: "direct" }, + senderId: "acct-b-user", + text: "unrelated", + }); + + const beforeMatch = await Promise.race([ + pending.then(() => "resolved"), + new Promise((resolve) => setTimeout(() => resolve("still-waiting"), 20)), + ]); + expect(beforeMatch).toBe("still-waiting"); + + state.addInboundMessage({ + accountId: "acct-a", + conversation: { id: "target", kind: "direct" }, + senderId: "acct-a-user", + text: "matched", + }); + + await expect(pending).resolves.toBeUndefined(); + }); + + it("wakes default-account cursor waits when accountId is omitted", async () => { + const state = createQaBusState(); + const pending = state.waitForCursorAdvance(0, 500, (snapshot) => { + return snapshot.events.some((event) => event.accountId === "default" && event.cursor > 0); + }); + + state.addInboundMessage({ + conversation: { id: "target", kind: "direct" }, + senderId: "default-user", + text: "matched", + }); + + await expect(pending).resolves.toBeUndefined(); + }); + it("preserves inline attachments and lets search match attachment metadata", () => { const state = createQaBusState(); diff --git a/extensions/qa-lab/src/bus-state.ts b/extensions/qa-lab/src/bus-state.ts index bb5aef491ef..f8c867631fb 100644 --- a/extensions/qa-lab/src/bus-state.ts +++ b/extensions/qa-lab/src/bus-state.ts @@ -23,6 +23,7 @@ import type { QaBusReadMessageInput, QaBusReactToMessageInput, QaBusSearchMessagesInput, + QaBusStateSnapshot, QaBusThread, QaBusWaitForInput, } from "./runtime-api.js"; @@ -282,6 +283,13 @@ export function createQaBusState() { async waitFor(input: QaBusWaitForInput) { return await waiters.waitFor(input); }, + async waitForCursorAdvance( + afterCursor: number, + timeoutMs: number, + shouldResolve?: (snapshot: QaBusStateSnapshot) => boolean, + ) { + return await waiters.waitForCursorAdvance(afterCursor, timeoutMs, shouldResolve); + }, }; } diff --git a/extensions/qa-lab/src/bus-waiters.ts b/extensions/qa-lab/src/bus-waiters.ts index 1d6c15671f6..68174276c08 100644 --- a/extensions/qa-lab/src/bus-waiters.ts +++ b/extensions/qa-lab/src/bus-waiters.ts @@ -17,6 +17,14 @@ type Waiter = { matcher: (snapshot: QaBusStateSnapshot) => QaBusWaitMatch | null; }; +type CursorWaiter = { + resolve: () => void; + reject: (error: Error) => void; + timer: NodeJS.Timeout; + afterCursor: number; + shouldResolve?: (snapshot: QaBusStateSnapshot) => boolean; +}; + function createQaBusMatcher( input: QaBusWaitForInput, ): (snapshot: QaBusStateSnapshot) => QaBusWaitMatch | null { @@ -39,6 +47,7 @@ function createQaBusMatcher( export function createQaBusWaiterStore(getSnapshot: () => QaBusStateSnapshot) { const waiters = new Set(); + const cursorWaiters = new Set(); return { reset(reason = "qa-bus reset") { @@ -47,9 +56,14 @@ export function createQaBusWaiterStore(getSnapshot: () => QaBusStateSnapshot) { waiter.reject(new Error(reason)); } waiters.clear(); + for (const waiter of cursorWaiters) { + clearTimeout(waiter.timer); + waiter.reject(new Error(reason)); + } + cursorWaiters.clear(); }, settle() { - if (waiters.size === 0) { + if (waiters.size === 0 && cursorWaiters.size === 0) { return; } const snapshot = getSnapshot(); @@ -62,6 +76,17 @@ export function createQaBusWaiterStore(getSnapshot: () => QaBusStateSnapshot) { waiters.delete(waiter); waiter.resolve(match); } + for (const waiter of Array.from(cursorWaiters)) { + if (snapshot.cursor <= waiter.afterCursor) { + continue; + } + if (waiter.shouldResolve && !waiter.shouldResolve(snapshot)) { + continue; + } + clearTimeout(waiter.timer); + cursorWaiters.delete(waiter); + waiter.resolve(); + } }, async waitFor(input: QaBusWaitForInput) { const matcher = createQaBusMatcher(input); @@ -83,5 +108,28 @@ export function createQaBusWaiterStore(getSnapshot: () => QaBusStateSnapshot) { waiters.add(waiter); }); }, + async waitForCursorAdvance( + afterCursor: number, + timeoutMs: number, + shouldResolve?: (snapshot: QaBusStateSnapshot) => boolean, + ) { + const snapshot = getSnapshot(); + if (snapshot.cursor > afterCursor && (!shouldResolve || shouldResolve(snapshot))) { + return; + } + return await new Promise((resolve, reject) => { + const waiter: CursorWaiter = { + resolve, + reject, + afterCursor, + shouldResolve, + timer: setTimeout(() => { + cursorWaiters.delete(waiter); + reject(new Error(`qa-bus wait timeout after ${timeoutMs}ms`)); + }, timeoutMs), + }; + cursorWaiters.add(waiter); + }); + }, }; } diff --git a/extensions/qa-lab/src/multipass.runtime.test.ts b/extensions/qa-lab/src/multipass.runtime.test.ts index a95caeda99b..72700c74591 100644 --- a/extensions/qa-lab/src/multipass.runtime.test.ts +++ b/extensions/qa-lab/src/multipass.runtime.test.ts @@ -181,6 +181,7 @@ describe("qa multipass runtime", () => { const fakeCodexHome = path.join(fakeHome, ".codex"); fs.mkdirSync(fakeCodexHome, { recursive: true }); vi.stubEnv("HOME", ""); + vi.stubEnv("CODEX_HOME", ""); vi.spyOn(os, "homedir").mockReturnValue(fakeHome); try { diff --git a/extensions/qa-lab/src/multipass.runtime.ts b/extensions/qa-lab/src/multipass.runtime.ts index aaef4a9e72a..6517708f453 100644 --- a/extensions/qa-lab/src/multipass.runtime.ts +++ b/extensions/qa-lab/src/multipass.runtime.ts @@ -280,8 +280,9 @@ function resolveQaLiveCliAuthEnv(baseEnv: NodeJS.ProcessEnv) { const codexHome = resolveUserPath(configuredCodexHome, baseEnv); return fs.existsSync(codexHome) ? { CODEX_HOME: codexHome } : {}; } - const hostHome = baseEnv.HOME?.trim() || os.homedir(); - const codexHome = path.join(hostHome, ".codex"); + const hostHome = baseEnv.HOME?.trim(); + const effectiveHome = hostHome || os.homedir(); + const codexHome = path.join(effectiveHome, ".codex"); return fs.existsSync(codexHome) ? { CODEX_HOME: codexHome } : {}; } diff --git a/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping-b.test.ts b/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping-b.test.ts index fa0fabc556e..6fe376aff77 100644 --- a/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping-b.test.ts +++ b/src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping-b.test.ts @@ -6,8 +6,10 @@ import "./test-helpers/fast-openclaw-tools.js"; import { createOpenClawCodingTools } from "./pi-tools.js"; describe("createOpenClawCodingTools", () => { + const testConfig: OpenClawConfig = {}; + it("preserves action enums in normalized schemas", () => { - const defaultTools = createOpenClawCodingTools({ senderIsOwner: true }); + const defaultTools = createOpenClawCodingTools({ config: testConfig, senderIsOwner: true }); const toolNames = ["canvas", "nodes", "cron", "gateway", "message"]; const missingNames = toolNames.filter( (name) => !defaultTools.some((candidate) => candidate.name === name), @@ -56,18 +58,20 @@ describe("createOpenClawCodingTools", () => { } }); it("enforces apply_patch availability and canonical names across model/provider constraints", () => { - const defaultTools = createOpenClawCodingTools({ senderIsOwner: true }); + const defaultTools = createOpenClawCodingTools({ config: testConfig, senderIsOwner: true }); expect(defaultTools.some((tool) => tool.name === "exec")).toBe(true); expect(defaultTools.some((tool) => tool.name === "process")).toBe(true); expect(defaultTools.some((tool) => tool.name === "apply_patch")).toBe(false); const openAiTools = createOpenClawCodingTools({ + config: testConfig, modelProvider: "openai", modelId: "gpt-5.4", }); expect(openAiTools.some((tool) => tool.name === "apply_patch")).toBe(true); const codexTools = createOpenClawCodingTools({ + config: testConfig, modelProvider: "openai-codex", modelId: "gpt-5.4", }); @@ -116,6 +120,7 @@ describe("createOpenClawCodingTools", () => { expect(denied.some((tool) => tool.name === "apply_patch")).toBe(false); const oauthTools = createOpenClawCodingTools({ + config: testConfig, modelProvider: "anthropic", modelAuthMode: "oauth", }); @@ -127,7 +132,7 @@ describe("createOpenClawCodingTools", () => { expect(names.has("apply_patch")).toBe(false); }); it("provides top-level object schemas for all tools", () => { - const tools = createOpenClawCodingTools(); + const tools = createOpenClawCodingTools({ config: testConfig }); const offenders = tools .map((tool) => { const schema = diff --git a/src/agents/skills-install-fallback.test.ts b/src/agents/skills-install-fallback.test.ts index 29800b6a5a8..71369283daa 100644 --- a/src/agents/skills-install-fallback.test.ts +++ b/src/agents/skills-install-fallback.test.ts @@ -120,6 +120,8 @@ describe("skills-install fallback edge cases", () => { }); it("handles sudo probe failures for go install without apt fallback", async () => { + vi.spyOn(process, "getuid").mockReturnValue(1000); + for (const testCase of [ { label: "sudo returns password required", @@ -130,8 +132,9 @@ describe("skills-install fallback edge cases", () => { stderr: "sudo: a password is required", }), assert: (result: { message: string; stderr: string }) => { - expect(result.message).toContain("sudo"); + expect(result.message).toContain("sudo is not usable"); expect(result.message).toContain("https://go.dev/doc/install"); + expect(result.stderr).toContain("sudo: a password is required"); }, }, { @@ -142,6 +145,7 @@ describe("skills-install fallback edge cases", () => { ), assert: (result: { message: string; stderr: string }) => { expect(result.message).toContain("sudo is not usable"); + expect(result.message).toContain("https://go.dev/doc/install"); expect(result.stderr).toContain("Executable not found"); }, }, @@ -167,6 +171,7 @@ describe("skills-install fallback edge cases", () => { }); it("status-selected go installer fails gracefully when apt fallback needs sudo", async () => { + vi.spyOn(process, "getuid").mockReturnValue(1000); mockAvailableBinaries(["apt-get", "sudo"]); runCommandWithTimeoutMock.mockResolvedValueOnce({ @@ -187,6 +192,7 @@ describe("skills-install fallback edge cases", () => { expect(result.ok).toBe(false); expect(result.message).toContain("sudo is not usable"); + expect(result.stderr).toContain("sudo: a password is required"); }); it("uv not installed and no brew returns helpful error without curl auto-install", async () => { diff --git a/src/gateway/server-methods/server-methods.test.ts b/src/gateway/server-methods/server-methods.test.ts index 86216e2406d..fc3a6c8eda8 100644 --- a/src/gateway/server-methods/server-methods.test.ts +++ b/src/gateway/server-methods/server-methods.test.ts @@ -339,7 +339,7 @@ describe("timestampOptsFromConfig", () => { { name: "falls back gracefully with empty config", cfg: {} as any, - expected: Intl.DateTimeFormat().resolvedOptions().timeZone, + expected: Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC", }, ])("$name", ({ cfg, expected }) => { expect(timestampOptsFromConfig(cfg).timezone).toBe(expected); diff --git a/src/plugins/install.test.ts b/src/plugins/install.test.ts index e92b22372e4..9e6f5a11b3f 100644 --- a/src/plugins/install.test.ts +++ b/src/plugins/install.test.ts @@ -1355,8 +1355,8 @@ describe("installPluginFromArchive", () => { expect(result.ok).toBe(false); if (!result.ok) { - expect(result.code).toBe(PLUGIN_INSTALL_ERROR_CODE.SECURITY_SCAN_FAILED); - expect(result.error).toContain("manifest dependency scan could not read"); + expect(result.code).toBe(PLUGIN_INSTALL_ERROR_CODE.SECURITY_SCAN_BLOCKED); + expect(result.error).toContain("plain-crypto-js"); expect(result.error).toContain("vendor/sealed"); } } finally { diff --git a/test/scripts/write-cli-startup-metadata.test.ts b/test/scripts/write-cli-startup-metadata.test.ts index 22eab7dc8c5..106c889a85a 100644 --- a/test/scripts/write-cli-startup-metadata.test.ts +++ b/test/scripts/write-cli-startup-metadata.test.ts @@ -1,23 +1,13 @@ import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import path from "node:path"; import { describe, expect, it } from "vitest"; -import { - renderBundledRootHelpText, - writeCliStartupMetadata, -} from "../../scripts/write-cli-startup-metadata.ts"; +import { writeCliStartupMetadata } from "../../scripts/write-cli-startup-metadata.ts"; import { createScriptTestHarness } from "./test-helpers.js"; describe("write-cli-startup-metadata", () => { const { createTempDir } = createScriptTestHarness(); - it("captures bundled root help text from the CLI program", async () => { - const rootHelpText = await renderBundledRootHelpText(); - - expect(rootHelpText).toContain("Usage:"); - expect(rootHelpText).toContain("openclaw"); - }); - - it("writes startup metadata with populated root help text", async () => { + it("writes startup metadata with populated root help text when dist falls back to source rendering", async () => { const tempRoot = createTempDir("openclaw-startup-metadata-"); const distDir = path.join(tempRoot, "dist"); const extensionsDir = path.join(tempRoot, "extensions");