mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00: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(),
|
logDebug: vi.fn(),
|
||||||
}));
|
}));
|
||||||
const { createService, shutdown, registerUnhandledRejectionHandler, logWarn, logDebug } = mocks;
|
const { createService, shutdown, registerUnhandledRejectionHandler, logWarn, logDebug } = mocks;
|
||||||
|
const getLoggerInfo = vi.fn();
|
||||||
|
|
||||||
const asString = (value: unknown, fallback: string) =>
|
const asString = (value: unknown, fallback: string) =>
|
||||||
typeof value === "string" && value.trim() ? value : fallback;
|
typeof value === "string" && value.trim() ? value : fallback;
|
||||||
@@ -81,7 +82,7 @@ describe("gateway bonjour advertiser", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.spyOn(logging, "getLogger").mockReturnValue({
|
vi.spyOn(logging, "getLogger").mockReturnValue({
|
||||||
info: (...args: unknown[]) => getLoggerInfo(...args),
|
info: (...args: unknown[]) => getLoggerInfo(...args),
|
||||||
});
|
} as unknown as ReturnType<typeof logging.getLogger>);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ function createForwarder(params: {
|
|||||||
const deliver = params.deliver ?? vi.fn().mockResolvedValue([]);
|
const deliver = params.deliver ?? vi.fn().mockResolvedValue([]);
|
||||||
const forwarder = createExecApprovalForwarder({
|
const forwarder = createExecApprovalForwarder({
|
||||||
getConfig: () => params.cfg,
|
getConfig: () => params.cfg,
|
||||||
deliver,
|
deliver: deliver as unknown as NonNullable<
|
||||||
|
NonNullable<Parameters<typeof createExecApprovalForwarder>[0]>["deliver"]
|
||||||
|
>,
|
||||||
nowMs: () => 1000,
|
nowMs: () => 1000,
|
||||||
resolveSessionTarget: params.resolveSessionTarget ?? (() => null),
|
resolveSessionTarget: params.resolveSessionTarget ?? (() => null),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -693,7 +693,7 @@ describe("exec approvals node host allowlist check", () => {
|
|||||||
|
|
||||||
describe("exec approvals default agent migration", () => {
|
describe("exec approvals default agent migration", () => {
|
||||||
it("migrates legacy default agent entries to main", () => {
|
it("migrates legacy default agent entries to main", () => {
|
||||||
const file = {
|
const file: ExecApprovalsFile = {
|
||||||
version: 1,
|
version: 1,
|
||||||
agents: {
|
agents: {
|
||||||
default: { allowlist: [{ pattern: "/bin/legacy" }] },
|
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", () => {
|
it("prefers main agent settings when both main and default exist", () => {
|
||||||
const file = {
|
const file: ExecApprovalsFile = {
|
||||||
version: 1,
|
version: 1,
|
||||||
agents: {
|
agents: {
|
||||||
main: { ask: "always", allowlist: [{ pattern: "/bin/main" }] },
|
main: { ask: "always", allowlist: [{ pattern: "/bin/main" }] },
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ describe("Ghost reminder bug (issue #13317)", () => {
|
|||||||
): Promise<{
|
): Promise<{
|
||||||
result: Awaited<ReturnType<typeof runHeartbeatOnce>>;
|
result: Awaited<ReturnType<typeof runHeartbeatOnce>>;
|
||||||
sendTelegram: ReturnType<typeof vi.fn>;
|
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 tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), tmpPrefix));
|
||||||
const sendTelegram = vi.fn().mockResolvedValue({
|
const sendTelegram = vi.fn().mockResolvedValue({
|
||||||
|
|||||||
@@ -744,7 +744,7 @@ describe("runHeartbeatOnce", () => {
|
|||||||
const forcedSessionKey = buildAgentPeerSessionKey({
|
const forcedSessionKey = buildAgentPeerSessionKey({
|
||||||
agentId,
|
agentId,
|
||||||
channel: "whatsapp",
|
channel: "whatsapp",
|
||||||
peerKind: "dm",
|
peerKind: "direct",
|
||||||
peerId: "+15559990000",
|
peerId: "+15559990000",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { startHeartbeatRunner } from "./heartbeat-runner.js";
|
|||||||
import { requestHeartbeatNow, resetHeartbeatWakeStateForTests } from "./heartbeat-wake.js";
|
import { requestHeartbeatNow, resetHeartbeatWakeStateForTests } from "./heartbeat-wake.js";
|
||||||
|
|
||||||
describe("startHeartbeatRunner", () => {
|
describe("startHeartbeatRunner", () => {
|
||||||
function startDefaultRunner(runOnce: (typeof startHeartbeatRunner)[0]["runOnce"]) {
|
function startDefaultRunner(runOnce: Parameters<typeof startHeartbeatRunner>[0]["runOnce"]) {
|
||||||
return startHeartbeatRunner({
|
return startHeartbeatRunner({
|
||||||
cfg: {
|
cfg: {
|
||||||
agents: { defaults: { heartbeat: { every: "30m" } } },
|
agents: { defaults: { heartbeat: { every: "30m" } } },
|
||||||
|
|||||||
@@ -75,13 +75,13 @@ describe("heartbeat transcript pruning", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Run heartbeat
|
// Run heartbeat
|
||||||
const cfg: OpenClawConfig = {
|
const cfg = {
|
||||||
version: 1,
|
version: 1,
|
||||||
model: "test-model",
|
model: "test-model",
|
||||||
agent: { workspace: tmpDir },
|
agent: { workspace: tmpDir },
|
||||||
sessionStore: storePath,
|
sessionStore: storePath,
|
||||||
channels: { telegram: { showOk: true, showAlerts: true } },
|
channels: { telegram: {} },
|
||||||
};
|
} as unknown as OpenClawConfig;
|
||||||
|
|
||||||
await runHeartbeatOnce({
|
await runHeartbeatOnce({
|
||||||
agentId: undefined,
|
agentId: undefined,
|
||||||
@@ -123,13 +123,13 @@ describe("heartbeat transcript pruning", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Run heartbeat
|
// Run heartbeat
|
||||||
const cfg: OpenClawConfig = {
|
const cfg = {
|
||||||
version: 1,
|
version: 1,
|
||||||
model: "test-model",
|
model: "test-model",
|
||||||
agent: { workspace: tmpDir },
|
agent: { workspace: tmpDir },
|
||||||
sessionStore: storePath,
|
sessionStore: storePath,
|
||||||
channels: { telegram: { showOk: true, showAlerts: true } },
|
channels: { telegram: {} },
|
||||||
};
|
} as unknown as OpenClawConfig;
|
||||||
|
|
||||||
await runHeartbeatOnce({
|
await runHeartbeatOnce({
|
||||||
agentId: undefined,
|
agentId: undefined,
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ describe("heartbeat-wake", () => {
|
|||||||
initialReason: string;
|
initialReason: string;
|
||||||
expectedRetryReason: string;
|
expectedRetryReason: string;
|
||||||
}) {
|
}) {
|
||||||
setHeartbeatWakeHandler(params.handler);
|
setHeartbeatWakeHandler(
|
||||||
|
params.handler as unknown as Parameters<typeof setHeartbeatWakeHandler>[0],
|
||||||
|
);
|
||||||
requestHeartbeatNow({ reason: params.initialReason, coalesceMs: 0 });
|
requestHeartbeatNow({ reason: params.initialReason, coalesceMs: 0 });
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(1);
|
await vi.advanceTimersByTimeAsync(1);
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ function redirectResponse(location: string): Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("fetchWithSsrFGuard hardening", () => {
|
describe("fetchWithSsrFGuard hardening", () => {
|
||||||
|
type LookupFn = NonNullable<Parameters<typeof fetchWithSsrFGuard>[0]["lookupFn"]>;
|
||||||
|
|
||||||
it("blocks private IP literal URLs before fetch", async () => {
|
it("blocks private IP literal URLs before fetch", async () => {
|
||||||
const fetchImpl = vi.fn();
|
const fetchImpl = vi.fn();
|
||||||
await expect(
|
await expect(
|
||||||
@@ -21,7 +23,9 @@ describe("fetchWithSsrFGuard hardening", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("blocks redirect chains that hop to private hosts", async () => {
|
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/"));
|
const fetchImpl = vi.fn().mockResolvedValueOnce(redirectResponse("http://127.0.0.1:6379/"));
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
@@ -47,7 +51,9 @@ describe("fetchWithSsrFGuard hardening", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("allows wildcard allowlisted hosts", async () => {
|
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 fetchImpl = vi.fn(async () => new Response("ok", { status: 200 }));
|
||||||
const result = await fetchWithSsrFGuard({
|
const result = await fetchWithSsrFGuard({
|
||||||
url: "https://img.assets.example.com/pic.png",
|
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", () => {
|
it("builds a delivery plan from session delivery context", () => {
|
||||||
const plan = resolveAgentDeliveryPlan({
|
const plan = resolveAgentDeliveryPlan({
|
||||||
sessionEntry: {
|
sessionEntry: {
|
||||||
|
sessionId: "s1",
|
||||||
|
updatedAt: 1,
|
||||||
deliveryContext: { channel: "whatsapp", to: "+1555", accountId: "work" },
|
deliveryContext: { channel: "whatsapp", to: "+1555", accountId: "work" },
|
||||||
},
|
},
|
||||||
requestedChannel: "last",
|
requestedChannel: "last",
|
||||||
@@ -36,6 +38,8 @@ describe("agent delivery helpers", () => {
|
|||||||
it("resolves fallback targets when no explicit destination is provided", () => {
|
it("resolves fallback targets when no explicit destination is provided", () => {
|
||||||
const plan = resolveAgentDeliveryPlan({
|
const plan = resolveAgentDeliveryPlan({
|
||||||
sessionEntry: {
|
sessionEntry: {
|
||||||
|
sessionId: "s2",
|
||||||
|
updatedAt: 2,
|
||||||
deliveryContext: { channel: "whatsapp" },
|
deliveryContext: { channel: "whatsapp" },
|
||||||
},
|
},
|
||||||
requestedChannel: "last",
|
requestedChannel: "last",
|
||||||
@@ -58,6 +62,8 @@ describe("agent delivery helpers", () => {
|
|||||||
it("skips outbound target resolution when explicit target validation is disabled", () => {
|
it("skips outbound target resolution when explicit target validation is disabled", () => {
|
||||||
const plan = resolveAgentDeliveryPlan({
|
const plan = resolveAgentDeliveryPlan({
|
||||||
sessionEntry: {
|
sessionEntry: {
|
||||||
|
sessionId: "s3",
|
||||||
|
updatedAt: 3,
|
||||||
deliveryContext: { channel: "whatsapp", to: "+1555" },
|
deliveryContext: { channel: "whatsapp", to: "+1555" },
|
||||||
},
|
},
|
||||||
requestedChannel: "last",
|
requestedChannel: "last",
|
||||||
|
|||||||
@@ -54,7 +54,9 @@ const whatsappChunkConfig: OpenClawConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function deliverWhatsAppPayload(params: {
|
async function deliverWhatsAppPayload(params: {
|
||||||
sendWhatsApp: ReturnType<typeof vi.fn>;
|
sendWhatsApp: NonNullable<
|
||||||
|
NonNullable<Parameters<typeof deliverOutboundPayloads>[0]["deps"]>["sendWhatsApp"]
|
||||||
|
>;
|
||||||
payload: { text: string; mediaUrl?: string };
|
payload: { text: string; mediaUrl?: string };
|
||||||
cfg?: OpenClawConfig;
|
cfg?: OpenClawConfig;
|
||||||
}) {
|
}) {
|
||||||
@@ -517,7 +519,7 @@ describe("deliverOutboundPayloads", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("emits message_sent success for text-only deliveries", async () => {
|
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" });
|
const sendWhatsApp = vi.fn().mockResolvedValue({ messageId: "w1", toJid: "jid" });
|
||||||
|
|
||||||
await deliverOutboundPayloads({
|
await deliverOutboundPayloads({
|
||||||
@@ -537,7 +539,7 @@ describe("deliverOutboundPayloads", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("emits message_sent success for sendPayload deliveries", async () => {
|
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 sendPayload = vi.fn().mockResolvedValue({ channel: "matrix", messageId: "mx-1" });
|
||||||
const sendText = vi.fn();
|
const sendText = vi.fn();
|
||||||
const sendMedia = vi.fn();
|
const sendMedia = vi.fn();
|
||||||
@@ -570,7 +572,7 @@ describe("deliverOutboundPayloads", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("emits message_sent failure when delivery errors", async () => {
|
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"));
|
const sendWhatsApp = vi.fn().mockRejectedValue(new Error("downstream failed"));
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
|||||||
@@ -558,6 +558,9 @@ describe("runMessageAction sandboxed media validation", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(result.kind).toBe("send");
|
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"));
|
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");
|
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"));
|
expect(result.sendResult?.mediaUrl).toBe(path.join(sandboxDir, "data", "note.ogg"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -446,7 +446,7 @@ describe("DirectoryCache", () => {
|
|||||||
describe("buildOutboundResultEnvelope", () => {
|
describe("buildOutboundResultEnvelope", () => {
|
||||||
it("flattens delivery-only payloads by default", () => {
|
it("flattens delivery-only payloads by default", () => {
|
||||||
const delivery: OutboundDeliveryJson = {
|
const delivery: OutboundDeliveryJson = {
|
||||||
provider: "whatsapp",
|
channel: "whatsapp",
|
||||||
via: "gateway",
|
via: "gateway",
|
||||||
to: "+1",
|
to: "+1",
|
||||||
messageId: "m1",
|
messageId: "m1",
|
||||||
@@ -468,7 +468,7 @@ describe("buildOutboundResultEnvelope", () => {
|
|||||||
|
|
||||||
it("includes delivery when payloads are present", () => {
|
it("includes delivery when payloads are present", () => {
|
||||||
const delivery: OutboundDeliveryJson = {
|
const delivery: OutboundDeliveryJson = {
|
||||||
provider: "telegram",
|
channel: "telegram",
|
||||||
via: "direct",
|
via: "direct",
|
||||||
to: "123",
|
to: "123",
|
||||||
messageId: "m2",
|
messageId: "m2",
|
||||||
@@ -489,7 +489,7 @@ describe("buildOutboundResultEnvelope", () => {
|
|||||||
|
|
||||||
it("can keep delivery wrapped when requested", () => {
|
it("can keep delivery wrapped when requested", () => {
|
||||||
const delivery: OutboundDeliveryJson = {
|
const delivery: OutboundDeliveryJson = {
|
||||||
provider: "discord",
|
channel: "discord",
|
||||||
via: "gateway",
|
via: "gateway",
|
||||||
to: "channel:C1",
|
to: "channel:C1",
|
||||||
messageId: "m3",
|
messageId: "m3",
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ describe("resolveMessagingTarget (directory fallback)", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("uses live directory fallback and caches the result", async () => {
|
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.listGroups.mockResolvedValue([]);
|
||||||
mocks.listGroupsLive.mockResolvedValue([entry]);
|
mocks.listGroupsLive.mockResolvedValue([entry]);
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const describeUnix = process.platform === "win32" ? describe.skip : describe;
|
|||||||
describe("ports helpers", () => {
|
describe("ports helpers", () => {
|
||||||
it("ensurePortAvailable rejects when port busy", async () => {
|
it("ensurePortAvailable rejects when port busy", async () => {
|
||||||
const server = net.createServer();
|
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;
|
const port = (server.address() as net.AddressInfo).port;
|
||||||
await expect(ensurePortAvailable(port)).rejects.toBeInstanceOf(PortInUseError);
|
await expect(ensurePortAvailable(port)).rejects.toBeInstanceOf(PortInUseError);
|
||||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
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 { EventEmitter } from "node:events";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
@@ -64,13 +64,15 @@ describe("ssh-config", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("returns null when ssh -G fails", async () => {
|
it("returns null when ssh -G fails", async () => {
|
||||||
spawnMock.mockImplementationOnce(() => {
|
spawnMock.mockImplementationOnce(
|
||||||
const { child } = createMockSpawnChild();
|
(_command: string, _args: readonly string[], _options: SpawnOptions): ChildProcess => {
|
||||||
process.nextTick(() => {
|
const { child } = createMockSpawnChild();
|
||||||
child.emit("exit", 1);
|
process.nextTick(() => {
|
||||||
});
|
child.emit("exit", 1);
|
||||||
return child;
|
});
|
||||||
});
|
return child as unknown as ChildProcess;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const { resolveSshConfig } = await import("./ssh-config.js");
|
const { resolveSshConfig } = await import("./ssh-config.js");
|
||||||
const config = await resolveSshConfig({ user: "me", host: "bad-host", port: 22 });
|
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 { describe, expect, it, vi } from "vitest";
|
||||||
import { POSIX_OPENCLAW_TMP_DIR, resolvePreferredOpenClawTmpDir } from "./tmp-openclaw-dir.js";
|
import { POSIX_OPENCLAW_TMP_DIR, resolvePreferredOpenClawTmpDir } from "./tmp-openclaw-dir.js";
|
||||||
|
|
||||||
|
type TmpDirOptions = NonNullable<Parameters<typeof resolvePreferredOpenClawTmpDir>[0]>;
|
||||||
|
|
||||||
function fallbackTmp(uid = 501) {
|
function fallbackTmp(uid = 501) {
|
||||||
return path.join("/var/fallback", `openclaw-${uid}`);
|
return path.join("/var/fallback", `openclaw-${uid}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveWithMocks(params: {
|
function resolveWithMocks(params: {
|
||||||
lstatSync: ReturnType<typeof vi.fn>;
|
lstatSync: NonNullable<TmpDirOptions["lstatSync"]>;
|
||||||
accessSync?: ReturnType<typeof vi.fn>;
|
accessSync?: NonNullable<TmpDirOptions["accessSync"]>;
|
||||||
uid?: number;
|
uid?: number;
|
||||||
tmpdirPath?: string;
|
tmpdirPath?: string;
|
||||||
}) {
|
}) {
|
||||||
@@ -28,7 +30,7 @@ function resolveWithMocks(params: {
|
|||||||
|
|
||||||
describe("resolvePreferredOpenClawTmpDir", () => {
|
describe("resolvePreferredOpenClawTmpDir", () => {
|
||||||
it("prefers /tmp/openclaw when it already exists and is writable", () => {
|
it("prefers /tmp/openclaw when it already exists and is writable", () => {
|
||||||
const lstatSync = vi.fn(() => ({
|
const lstatSync: NonNullable<TmpDirOptions["lstatSync"]> = vi.fn(() => ({
|
||||||
isDirectory: () => true,
|
isDirectory: () => true,
|
||||||
isSymbolicLink: () => false,
|
isSymbolicLink: () => false,
|
||||||
uid: 501,
|
uid: 501,
|
||||||
@@ -43,26 +45,28 @@ describe("resolvePreferredOpenClawTmpDir", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("prefers /tmp/openclaw when it does not exist but /tmp is writable", () => {
|
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 };
|
const err = new Error("missing") as Error & { code?: string };
|
||||||
err.code = "ENOENT";
|
err.code = "ENOENT";
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
// second lstat call (after mkdir) should succeed
|
// second lstat call (after mkdir) should succeed
|
||||||
lstatSync.mockImplementationOnce(() => {
|
lstatSyncMock.mockImplementationOnce(() => {
|
||||||
const err = new Error("missing") as Error & { code?: string };
|
const err = new Error("missing") as Error & { code?: string };
|
||||||
err.code = "ENOENT";
|
err.code = "ENOENT";
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
lstatSync.mockImplementationOnce(() => ({
|
lstatSyncMock.mockImplementationOnce(() => ({
|
||||||
isDirectory: () => true,
|
isDirectory: () => true,
|
||||||
isSymbolicLink: () => false,
|
isSymbolicLink: () => false,
|
||||||
uid: 501,
|
uid: 501,
|
||||||
mode: 0o40700,
|
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(resolved).toBe(POSIX_OPENCLAW_TMP_DIR);
|
||||||
expect(accessSync).toHaveBeenCalledWith("/tmp", expect.any(Number));
|
expect(accessSync).toHaveBeenCalledWith("/tmp", expect.any(Number));
|
||||||
@@ -76,7 +80,7 @@ describe("resolvePreferredOpenClawTmpDir", () => {
|
|||||||
isSymbolicLink: () => false,
|
isSymbolicLink: () => false,
|
||||||
uid: 501,
|
uid: 501,
|
||||||
mode: 0o100644,
|
mode: 0o100644,
|
||||||
}));
|
})) as unknown as ReturnType<typeof vi.fn> & NonNullable<TmpDirOptions["lstatSync"]>;
|
||||||
const { resolved, tmpdir } = resolveWithMocks({ lstatSync });
|
const { resolved, tmpdir } = resolveWithMocks({ lstatSync });
|
||||||
|
|
||||||
expect(resolved).toBe(fallbackTmp());
|
expect(resolved).toBe(fallbackTmp());
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ describe("installUnhandledRejectionHandler - fatal detection", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
exitCalls = [];
|
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) {
|
if (code !== undefined && code !== null) {
|
||||||
exitCalls.push(code);
|
exitCalls.push(code);
|
||||||
}
|
}
|
||||||
|
throw new Error("process.exit");
|
||||||
});
|
});
|
||||||
|
|
||||||
consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||||
|
|||||||
Reference in New Issue
Block a user