mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
chore: Fix types in tests 33/N.
This commit is contained in:
22
scripts/run-node.d.mts
Normal file
22
scripts/run-node.d.mts
Normal file
@@ -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<number>;
|
||||
14
scripts/watch-node.d.mts
Normal file
14
scripts/watch-node.d.mts
Normal file
@@ -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<number>;
|
||||
@@ -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<typeof logging.getLogger>);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -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<Parameters<typeof createExecApprovalForwarder>[0]>["deliver"]
|
||||
>,
|
||||
nowMs: () => 1000,
|
||||
resolveSessionTarget: params.resolveSessionTarget ?? (() => null),
|
||||
});
|
||||
|
||||
@@ -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" }] },
|
||||
|
||||
@@ -92,7 +92,7 @@ describe("Ghost reminder bug (issue #13317)", () => {
|
||||
): Promise<{
|
||||
result: Awaited<ReturnType<typeof runHeartbeatOnce>>;
|
||||
sendTelegram: ReturnType<typeof vi.fn>;
|
||||
getReplySpy: ReturnType<typeof vi.spyOn<typeof replyModule, "getReplyFromConfig">>;
|
||||
getReplySpy: ReturnType<typeof vi.fn>;
|
||||
}> => {
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), tmpPrefix));
|
||||
const sendTelegram = vi.fn().mockResolvedValue({
|
||||
|
||||
@@ -744,7 +744,7 @@ describe("runHeartbeatOnce", () => {
|
||||
const forcedSessionKey = buildAgentPeerSessionKey({
|
||||
agentId,
|
||||
channel: "whatsapp",
|
||||
peerKind: "dm",
|
||||
peerKind: "direct",
|
||||
peerId: "+15559990000",
|
||||
});
|
||||
|
||||
|
||||
@@ -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<typeof startHeartbeatRunner>[0]["runOnce"]) {
|
||||
return startHeartbeatRunner({
|
||||
cfg: {
|
||||
agents: { defaults: { heartbeat: { every: "30m" } } },
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -13,7 +13,9 @@ describe("heartbeat-wake", () => {
|
||||
initialReason: string;
|
||||
expectedRetryReason: string;
|
||||
}) {
|
||||
setHeartbeatWakeHandler(params.handler);
|
||||
setHeartbeatWakeHandler(
|
||||
params.handler as unknown as Parameters<typeof setHeartbeatWakeHandler>[0],
|
||||
);
|
||||
requestHeartbeatNow({ reason: params.initialReason, coalesceMs: 0 });
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1);
|
||||
|
||||
@@ -9,6 +9,8 @@ function redirectResponse(location: string): Response {
|
||||
}
|
||||
|
||||
describe("fetchWithSsrFGuard hardening", () => {
|
||||
type LookupFn = NonNullable<Parameters<typeof fetchWithSsrFGuard>[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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -54,7 +54,9 @@ const whatsappChunkConfig: OpenClawConfig = {
|
||||
};
|
||||
|
||||
async function deliverWhatsAppPayload(params: {
|
||||
sendWhatsApp: ReturnType<typeof vi.fn>;
|
||||
sendWhatsApp: NonNullable<
|
||||
NonNullable<Parameters<typeof deliverOutboundPayloads>[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(
|
||||
|
||||
@@ -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"));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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]);
|
||||
|
||||
|
||||
@@ -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<void>((resolve) => server.listen(0, () => resolve()));
|
||||
const port = (server.address() as net.AddressInfo).port;
|
||||
await expect(ensurePortAvailable(port)).rejects.toBeInstanceOf(PortInUseError);
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
|
||||
38
src/infra/scripts-modules.d.ts
vendored
Normal file
38
src/infra/scripts-modules.d.ts
vendored
Normal file
@@ -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<number>;
|
||||
}
|
||||
|
||||
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<number>;
|
||||
}
|
||||
@@ -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 });
|
||||
|
||||
@@ -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<Parameters<typeof resolvePreferredOpenClawTmpDir>[0]>;
|
||||
|
||||
function fallbackTmp(uid = 501) {
|
||||
return path.join("/var/fallback", `openclaw-${uid}`);
|
||||
}
|
||||
|
||||
function resolveWithMocks(params: {
|
||||
lstatSync: ReturnType<typeof vi.fn>;
|
||||
accessSync?: ReturnType<typeof vi.fn>;
|
||||
lstatSync: NonNullable<TmpDirOptions["lstatSync"]>;
|
||||
accessSync?: NonNullable<TmpDirOptions["accessSync"]>;
|
||||
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<TmpDirOptions["lstatSync"]> = 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<NonNullable<TmpDirOptions["lstatSync"]>>(() => {
|
||||
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<typeof vi.fn> & NonNullable<TmpDirOptions["lstatSync"]>;
|
||||
const { resolved, tmpdir } = resolveWithMocks({ lstatSync });
|
||||
|
||||
expect(resolved).toBe(fallbackTmp());
|
||||
|
||||
@@ -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(() => {});
|
||||
|
||||
Reference in New Issue
Block a user