From 49bd9f75f4cd5290db94292cee406fec05d54895 Mon Sep 17 00:00:00 2001 From: cpojer Date: Tue, 17 Feb 2026 15:45:36 +0900 Subject: [PATCH] chore: Fix types in tests 33/N. --- scripts/run-node.d.mts | 22 +++++++++++ scripts/watch-node.d.mts | 14 +++++++ src/infra/bonjour.test.ts | 3 +- src/infra/exec-approval-forwarder.test.ts | 4 +- src/infra/exec-approvals.test.ts | 4 +- .../heartbeat-runner.ghost-reminder.test.ts | 2 +- ...tbeat-runner.returns-default-unset.test.ts | 2 +- src/infra/heartbeat-runner.scheduler.test.ts | 2 +- .../heartbeat-runner.transcript-prune.test.ts | 12 +++--- src/infra/heartbeat-wake.test.ts | 4 +- src/infra/net/fetch-guard.ssrf.test.ts | 10 ++++- src/infra/outbound/agent-delivery.test.ts | 6 +++ src/infra/outbound/deliver.test.ts | 10 +++-- .../outbound/message-action-runner.test.ts | 6 +++ src/infra/outbound/outbound.test.ts | 6 +-- src/infra/outbound/target-resolver.test.ts | 2 +- src/infra/ports.test.ts | 2 +- src/infra/scripts-modules.d.ts | 38 +++++++++++++++++++ src/infra/ssh-config.test.ts | 18 +++++---- src/infra/tmp-openclaw-dir.test.ts | 20 ++++++---- ...handled-rejections.fatal-detection.test.ts | 3 +- 21 files changed, 148 insertions(+), 42 deletions(-) create mode 100644 scripts/run-node.d.mts create mode 100644 scripts/watch-node.d.mts create mode 100644 src/infra/scripts-modules.d.ts diff --git a/scripts/run-node.d.mts b/scripts/run-node.d.mts new file mode 100644 index 0000000000..1fc9a1437e --- /dev/null +++ b/scripts/run-node.d.mts @@ -0,0 +1,22 @@ +export const runNodeWatchedPaths: string[]; + +export function runNodeMain(params?: { + spawn?: ( + cmd: string, + args: string[], + options: unknown, + ) => { + on: ( + event: "exit", + cb: (code: number | null, signal: string | null) => void, + ) => void | undefined; + }; + spawnSync?: unknown; + fs?: unknown; + stderr?: { write: (value: string) => void }; + execPath?: string; + cwd?: string; + args?: string[]; + env?: NodeJS.ProcessEnv; + platform?: NodeJS.Platform; +}): Promise; diff --git a/scripts/watch-node.d.mts b/scripts/watch-node.d.mts new file mode 100644 index 0000000000..d0e9dd9375 --- /dev/null +++ b/scripts/watch-node.d.mts @@ -0,0 +1,14 @@ +export function runWatchMain(params?: { + spawn?: ( + cmd: string, + args: string[], + options: unknown, + ) => { + on: (event: "exit", cb: (code: number | null, signal: string | null) => void) => void; + }; + process?: NodeJS.Process; + cwd?: string; + args?: string[]; + env?: NodeJS.ProcessEnv; + now?: () => number; +}): Promise; diff --git a/src/infra/bonjour.test.ts b/src/infra/bonjour.test.ts index 7980ab4bbd..2e2b6d81d2 100644 --- a/src/infra/bonjour.test.ts +++ b/src/infra/bonjour.test.ts @@ -10,6 +10,7 @@ const mocks = vi.hoisted(() => ({ logDebug: vi.fn(), })); const { createService, shutdown, registerUnhandledRejectionHandler, logWarn, logDebug } = mocks; +const getLoggerInfo = vi.fn(); const asString = (value: unknown, fallback: string) => typeof value === "string" && value.trim() ? value : fallback; @@ -81,7 +82,7 @@ describe("gateway bonjour advertiser", () => { beforeEach(() => { vi.spyOn(logging, "getLogger").mockReturnValue({ info: (...args: unknown[]) => getLoggerInfo(...args), - }); + } as unknown as ReturnType); }); afterEach(() => { diff --git a/src/infra/exec-approval-forwarder.test.ts b/src/infra/exec-approval-forwarder.test.ts index 83c322d3b4..bc1eba50a0 100644 --- a/src/infra/exec-approval-forwarder.test.ts +++ b/src/infra/exec-approval-forwarder.test.ts @@ -42,7 +42,9 @@ function createForwarder(params: { const deliver = params.deliver ?? vi.fn().mockResolvedValue([]); const forwarder = createExecApprovalForwarder({ getConfig: () => params.cfg, - deliver, + deliver: deliver as unknown as NonNullable< + NonNullable[0]>["deliver"] + >, nowMs: () => 1000, resolveSessionTarget: params.resolveSessionTarget ?? (() => null), }); diff --git a/src/infra/exec-approvals.test.ts b/src/infra/exec-approvals.test.ts index f263e00eaa..3959b2ef4c 100644 --- a/src/infra/exec-approvals.test.ts +++ b/src/infra/exec-approvals.test.ts @@ -693,7 +693,7 @@ describe("exec approvals node host allowlist check", () => { describe("exec approvals default agent migration", () => { it("migrates legacy default agent entries to main", () => { - const file = { + const file: ExecApprovalsFile = { version: 1, agents: { default: { allowlist: [{ pattern: "/bin/legacy" }] }, @@ -706,7 +706,7 @@ describe("exec approvals default agent migration", () => { }); it("prefers main agent settings when both main and default exist", () => { - const file = { + const file: ExecApprovalsFile = { version: 1, agents: { main: { ask: "always", allowlist: [{ pattern: "/bin/main" }] }, diff --git a/src/infra/heartbeat-runner.ghost-reminder.test.ts b/src/infra/heartbeat-runner.ghost-reminder.test.ts index af7ffbf436..28bf9a310a 100644 --- a/src/infra/heartbeat-runner.ghost-reminder.test.ts +++ b/src/infra/heartbeat-runner.ghost-reminder.test.ts @@ -92,7 +92,7 @@ describe("Ghost reminder bug (issue #13317)", () => { ): Promise<{ result: Awaited>; sendTelegram: ReturnType; - getReplySpy: ReturnType>; + getReplySpy: ReturnType; }> => { const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), tmpPrefix)); const sendTelegram = vi.fn().mockResolvedValue({ diff --git a/src/infra/heartbeat-runner.returns-default-unset.test.ts b/src/infra/heartbeat-runner.returns-default-unset.test.ts index 1ce3871d72..f866af4470 100644 --- a/src/infra/heartbeat-runner.returns-default-unset.test.ts +++ b/src/infra/heartbeat-runner.returns-default-unset.test.ts @@ -744,7 +744,7 @@ describe("runHeartbeatOnce", () => { const forcedSessionKey = buildAgentPeerSessionKey({ agentId, channel: "whatsapp", - peerKind: "dm", + peerKind: "direct", peerId: "+15559990000", }); diff --git a/src/infra/heartbeat-runner.scheduler.test.ts b/src/infra/heartbeat-runner.scheduler.test.ts index c6908e07b8..19fa457908 100644 --- a/src/infra/heartbeat-runner.scheduler.test.ts +++ b/src/infra/heartbeat-runner.scheduler.test.ts @@ -4,7 +4,7 @@ import { startHeartbeatRunner } from "./heartbeat-runner.js"; import { requestHeartbeatNow, resetHeartbeatWakeStateForTests } from "./heartbeat-wake.js"; describe("startHeartbeatRunner", () => { - function startDefaultRunner(runOnce: (typeof startHeartbeatRunner)[0]["runOnce"]) { + function startDefaultRunner(runOnce: Parameters[0]["runOnce"]) { return startHeartbeatRunner({ cfg: { agents: { defaults: { heartbeat: { every: "30m" } } }, diff --git a/src/infra/heartbeat-runner.transcript-prune.test.ts b/src/infra/heartbeat-runner.transcript-prune.test.ts index cea7f17249..c578d2fa3e 100644 --- a/src/infra/heartbeat-runner.transcript-prune.test.ts +++ b/src/infra/heartbeat-runner.transcript-prune.test.ts @@ -75,13 +75,13 @@ describe("heartbeat transcript pruning", () => { }); // Run heartbeat - const cfg: OpenClawConfig = { + const cfg = { version: 1, model: "test-model", agent: { workspace: tmpDir }, sessionStore: storePath, - channels: { telegram: { showOk: true, showAlerts: true } }, - }; + channels: { telegram: {} }, + } as unknown as OpenClawConfig; await runHeartbeatOnce({ agentId: undefined, @@ -123,13 +123,13 @@ describe("heartbeat transcript pruning", () => { }); // Run heartbeat - const cfg: OpenClawConfig = { + const cfg = { version: 1, model: "test-model", agent: { workspace: tmpDir }, sessionStore: storePath, - channels: { telegram: { showOk: true, showAlerts: true } }, - }; + channels: { telegram: {} }, + } as unknown as OpenClawConfig; await runHeartbeatOnce({ agentId: undefined, diff --git a/src/infra/heartbeat-wake.test.ts b/src/infra/heartbeat-wake.test.ts index 2cda1771b8..1f800c655e 100644 --- a/src/infra/heartbeat-wake.test.ts +++ b/src/infra/heartbeat-wake.test.ts @@ -13,7 +13,9 @@ describe("heartbeat-wake", () => { initialReason: string; expectedRetryReason: string; }) { - setHeartbeatWakeHandler(params.handler); + setHeartbeatWakeHandler( + params.handler as unknown as Parameters[0], + ); requestHeartbeatNow({ reason: params.initialReason, coalesceMs: 0 }); await vi.advanceTimersByTimeAsync(1); diff --git a/src/infra/net/fetch-guard.ssrf.test.ts b/src/infra/net/fetch-guard.ssrf.test.ts index 804b53439d..a4722d2b26 100644 --- a/src/infra/net/fetch-guard.ssrf.test.ts +++ b/src/infra/net/fetch-guard.ssrf.test.ts @@ -9,6 +9,8 @@ function redirectResponse(location: string): Response { } describe("fetchWithSsrFGuard hardening", () => { + type LookupFn = NonNullable[0]["lookupFn"]>; + it("blocks private IP literal URLs before fetch", async () => { const fetchImpl = vi.fn(); await expect( @@ -21,7 +23,9 @@ describe("fetchWithSsrFGuard hardening", () => { }); it("blocks redirect chains that hop to private hosts", async () => { - const lookupFn = vi.fn(async () => [{ address: "93.184.216.34", family: 4 }]); + const lookupFn = vi.fn(async () => [ + { address: "93.184.216.34", family: 4 }, + ]) as unknown as LookupFn; const fetchImpl = vi.fn().mockResolvedValueOnce(redirectResponse("http://127.0.0.1:6379/")); await expect( @@ -47,7 +51,9 @@ describe("fetchWithSsrFGuard hardening", () => { }); it("allows wildcard allowlisted hosts", async () => { - const lookupFn = vi.fn(async () => [{ address: "93.184.216.34", family: 4 }]); + const lookupFn = vi.fn(async () => [ + { address: "93.184.216.34", family: 4 }, + ]) as unknown as LookupFn; const fetchImpl = vi.fn(async () => new Response("ok", { status: 200 })); const result = await fetchWithSsrFGuard({ url: "https://img.assets.example.com/pic.png", diff --git a/src/infra/outbound/agent-delivery.test.ts b/src/infra/outbound/agent-delivery.test.ts index ac76d327a6..8f2cbb23ea 100644 --- a/src/infra/outbound/agent-delivery.test.ts +++ b/src/infra/outbound/agent-delivery.test.ts @@ -19,6 +19,8 @@ describe("agent delivery helpers", () => { it("builds a delivery plan from session delivery context", () => { const plan = resolveAgentDeliveryPlan({ sessionEntry: { + sessionId: "s1", + updatedAt: 1, deliveryContext: { channel: "whatsapp", to: "+1555", accountId: "work" }, }, requestedChannel: "last", @@ -36,6 +38,8 @@ describe("agent delivery helpers", () => { it("resolves fallback targets when no explicit destination is provided", () => { const plan = resolveAgentDeliveryPlan({ sessionEntry: { + sessionId: "s2", + updatedAt: 2, deliveryContext: { channel: "whatsapp" }, }, requestedChannel: "last", @@ -58,6 +62,8 @@ describe("agent delivery helpers", () => { it("skips outbound target resolution when explicit target validation is disabled", () => { const plan = resolveAgentDeliveryPlan({ sessionEntry: { + sessionId: "s3", + updatedAt: 3, deliveryContext: { channel: "whatsapp", to: "+1555" }, }, requestedChannel: "last", diff --git a/src/infra/outbound/deliver.test.ts b/src/infra/outbound/deliver.test.ts index ba9d2013c4..91760eb843 100644 --- a/src/infra/outbound/deliver.test.ts +++ b/src/infra/outbound/deliver.test.ts @@ -54,7 +54,9 @@ const whatsappChunkConfig: OpenClawConfig = { }; async function deliverWhatsAppPayload(params: { - sendWhatsApp: ReturnType; + sendWhatsApp: NonNullable< + NonNullable[0]["deps"]>["sendWhatsApp"] + >; payload: { text: string; mediaUrl?: string }; cfg?: OpenClawConfig; }) { @@ -517,7 +519,7 @@ describe("deliverOutboundPayloads", () => { }); it("emits message_sent success for text-only deliveries", async () => { - hookMocks.runner.hasHooks.mockImplementation((name: string) => name === "message_sent"); + hookMocks.runner.hasHooks.mockReturnValue(true); const sendWhatsApp = vi.fn().mockResolvedValue({ messageId: "w1", toJid: "jid" }); await deliverOutboundPayloads({ @@ -537,7 +539,7 @@ describe("deliverOutboundPayloads", () => { }); it("emits message_sent success for sendPayload deliveries", async () => { - hookMocks.runner.hasHooks.mockImplementation((name: string) => name === "message_sent"); + hookMocks.runner.hasHooks.mockReturnValue(true); const sendPayload = vi.fn().mockResolvedValue({ channel: "matrix", messageId: "mx-1" }); const sendText = vi.fn(); const sendMedia = vi.fn(); @@ -570,7 +572,7 @@ describe("deliverOutboundPayloads", () => { }); it("emits message_sent failure when delivery errors", async () => { - hookMocks.runner.hasHooks.mockImplementation((name: string) => name === "message_sent"); + hookMocks.runner.hasHooks.mockReturnValue(true); const sendWhatsApp = vi.fn().mockRejectedValue(new Error("downstream failed")); await expect( diff --git a/src/infra/outbound/message-action-runner.test.ts b/src/infra/outbound/message-action-runner.test.ts index 710e347be8..c273d52ddb 100644 --- a/src/infra/outbound/message-action-runner.test.ts +++ b/src/infra/outbound/message-action-runner.test.ts @@ -558,6 +558,9 @@ describe("runMessageAction sandboxed media validation", () => { }); expect(result.kind).toBe("send"); + if (result.kind !== "send") { + throw new Error("expected send result"); + } expect(result.sendResult?.mediaUrl).toBe(path.join(sandboxDir, "data", "file.txt")); }); }); @@ -575,6 +578,9 @@ describe("runMessageAction sandboxed media validation", () => { }); expect(result.kind).toBe("send"); + if (result.kind !== "send") { + throw new Error("expected send result"); + } expect(result.sendResult?.mediaUrl).toBe(path.join(sandboxDir, "data", "note.ogg")); }); }); diff --git a/src/infra/outbound/outbound.test.ts b/src/infra/outbound/outbound.test.ts index 97c833cc8c..d113f7530c 100644 --- a/src/infra/outbound/outbound.test.ts +++ b/src/infra/outbound/outbound.test.ts @@ -446,7 +446,7 @@ describe("DirectoryCache", () => { describe("buildOutboundResultEnvelope", () => { it("flattens delivery-only payloads by default", () => { const delivery: OutboundDeliveryJson = { - provider: "whatsapp", + channel: "whatsapp", via: "gateway", to: "+1", messageId: "m1", @@ -468,7 +468,7 @@ describe("buildOutboundResultEnvelope", () => { it("includes delivery when payloads are present", () => { const delivery: OutboundDeliveryJson = { - provider: "telegram", + channel: "telegram", via: "direct", to: "123", messageId: "m2", @@ -489,7 +489,7 @@ describe("buildOutboundResultEnvelope", () => { it("can keep delivery wrapped when requested", () => { const delivery: OutboundDeliveryJson = { - provider: "discord", + channel: "discord", via: "gateway", to: "channel:C1", messageId: "m3", diff --git a/src/infra/outbound/target-resolver.test.ts b/src/infra/outbound/target-resolver.test.ts index 3b9c37486d..6ffed273c2 100644 --- a/src/infra/outbound/target-resolver.test.ts +++ b/src/infra/outbound/target-resolver.test.ts @@ -31,7 +31,7 @@ describe("resolveMessagingTarget (directory fallback)", () => { }); it("uses live directory fallback and caches the result", async () => { - const entry: ChannelDirectoryEntry = { id: "123456789", name: "support" }; + const entry: ChannelDirectoryEntry = { kind: "group", id: "123456789", name: "support" }; mocks.listGroups.mockResolvedValue([]); mocks.listGroupsLive.mockResolvedValue([entry]); diff --git a/src/infra/ports.test.ts b/src/infra/ports.test.ts index a58ca4e431..10027e2452 100644 --- a/src/infra/ports.test.ts +++ b/src/infra/ports.test.ts @@ -22,7 +22,7 @@ const describeUnix = process.platform === "win32" ? describe.skip : describe; describe("ports helpers", () => { it("ensurePortAvailable rejects when port busy", async () => { const server = net.createServer(); - await new Promise((resolve) => server.listen(0, resolve)); + await new Promise((resolve) => server.listen(0, () => resolve())); const port = (server.address() as net.AddressInfo).port; await expect(ensurePortAvailable(port)).rejects.toBeInstanceOf(PortInUseError); await new Promise((resolve) => server.close(() => resolve())); diff --git a/src/infra/scripts-modules.d.ts b/src/infra/scripts-modules.d.ts new file mode 100644 index 0000000000..e7918daa31 --- /dev/null +++ b/src/infra/scripts-modules.d.ts @@ -0,0 +1,38 @@ +declare module "../../scripts/run-node.mjs" { + export const runNodeWatchedPaths: string[]; + export function runNodeMain(params?: { + spawn?: ( + cmd: string, + args: string[], + options: unknown, + ) => { + on: ( + event: "exit", + cb: (code: number | null, signal: string | null) => void, + ) => void | undefined; + }; + spawnSync?: unknown; + fs?: unknown; + stderr?: { write: (value: string) => void }; + execPath?: string; + cwd?: string; + args?: string[]; + env?: NodeJS.ProcessEnv; + platform?: NodeJS.Platform; + }): Promise; +} + +declare module "../../scripts/watch-node.mjs" { + export function runWatchMain(params?: { + spawn?: ( + cmd: string, + args: string[], + options: unknown, + ) => { on: (event: "exit", cb: (code: number | null, signal: string | null) => void) => void }; + process?: NodeJS.Process; + cwd?: string; + args?: string[]; + env?: NodeJS.ProcessEnv; + now?: () => number; + }): Promise; +} diff --git a/src/infra/ssh-config.test.ts b/src/infra/ssh-config.test.ts index 7ea70fb8b8..53937cc245 100644 --- a/src/infra/ssh-config.test.ts +++ b/src/infra/ssh-config.test.ts @@ -1,4 +1,4 @@ -import { spawn } from "node:child_process"; +import { spawn, type ChildProcess, type SpawnOptions } from "node:child_process"; import { EventEmitter } from "node:events"; import { describe, expect, it, vi } from "vitest"; @@ -64,13 +64,15 @@ describe("ssh-config", () => { }); it("returns null when ssh -G fails", async () => { - spawnMock.mockImplementationOnce(() => { - const { child } = createMockSpawnChild(); - process.nextTick(() => { - child.emit("exit", 1); - }); - return child; - }); + spawnMock.mockImplementationOnce( + (_command: string, _args: readonly string[], _options: SpawnOptions): ChildProcess => { + const { child } = createMockSpawnChild(); + process.nextTick(() => { + child.emit("exit", 1); + }); + return child as unknown as ChildProcess; + }, + ); const { resolveSshConfig } = await import("./ssh-config.js"); const config = await resolveSshConfig({ user: "me", host: "bad-host", port: 22 }); diff --git a/src/infra/tmp-openclaw-dir.test.ts b/src/infra/tmp-openclaw-dir.test.ts index d4f0d2a255..0424e5e022 100644 --- a/src/infra/tmp-openclaw-dir.test.ts +++ b/src/infra/tmp-openclaw-dir.test.ts @@ -2,13 +2,15 @@ import path from "node:path"; import { describe, expect, it, vi } from "vitest"; import { POSIX_OPENCLAW_TMP_DIR, resolvePreferredOpenClawTmpDir } from "./tmp-openclaw-dir.js"; +type TmpDirOptions = NonNullable[0]>; + function fallbackTmp(uid = 501) { return path.join("/var/fallback", `openclaw-${uid}`); } function resolveWithMocks(params: { - lstatSync: ReturnType; - accessSync?: ReturnType; + lstatSync: NonNullable; + accessSync?: NonNullable; uid?: number; tmpdirPath?: string; }) { @@ -28,7 +30,7 @@ function resolveWithMocks(params: { describe("resolvePreferredOpenClawTmpDir", () => { it("prefers /tmp/openclaw when it already exists and is writable", () => { - const lstatSync = vi.fn(() => ({ + const lstatSync: NonNullable = vi.fn(() => ({ isDirectory: () => true, isSymbolicLink: () => false, uid: 501, @@ -43,26 +45,28 @@ describe("resolvePreferredOpenClawTmpDir", () => { }); it("prefers /tmp/openclaw when it does not exist but /tmp is writable", () => { - const lstatSync = vi.fn(() => { + const lstatSyncMock = vi.fn>(() => { const err = new Error("missing") as Error & { code?: string }; err.code = "ENOENT"; throw err; }); // second lstat call (after mkdir) should succeed - lstatSync.mockImplementationOnce(() => { + lstatSyncMock.mockImplementationOnce(() => { const err = new Error("missing") as Error & { code?: string }; err.code = "ENOENT"; throw err; }); - lstatSync.mockImplementationOnce(() => ({ + lstatSyncMock.mockImplementationOnce(() => ({ isDirectory: () => true, isSymbolicLink: () => false, uid: 501, mode: 0o40700, })); - const { resolved, accessSync, mkdirSync, tmpdir } = resolveWithMocks({ lstatSync }); + const { resolved, accessSync, mkdirSync, tmpdir } = resolveWithMocks({ + lstatSync: lstatSyncMock, + }); expect(resolved).toBe(POSIX_OPENCLAW_TMP_DIR); expect(accessSync).toHaveBeenCalledWith("/tmp", expect.any(Number)); @@ -76,7 +80,7 @@ describe("resolvePreferredOpenClawTmpDir", () => { isSymbolicLink: () => false, uid: 501, mode: 0o100644, - })); + })) as unknown as ReturnType & NonNullable; const { resolved, tmpdir } = resolveWithMocks({ lstatSync }); expect(resolved).toBe(fallbackTmp()); diff --git a/src/infra/unhandled-rejections.fatal-detection.test.ts b/src/infra/unhandled-rejections.fatal-detection.test.ts index 76cc225682..d8daa8e635 100644 --- a/src/infra/unhandled-rejections.fatal-detection.test.ts +++ b/src/infra/unhandled-rejections.fatal-detection.test.ts @@ -16,10 +16,11 @@ describe("installUnhandledRejectionHandler - fatal detection", () => { beforeEach(() => { exitCalls = []; - vi.spyOn(process, "exit").mockImplementation((code: string | number | null | undefined) => { + vi.spyOn(process, "exit").mockImplementation((code?: string | number | null): never => { if (code !== undefined && code !== null) { exitCalls.push(code); } + throw new Error("process.exit"); }); consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});