mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 18:14:31 +00:00
refactor(tests): share outbound runner and delivery helpers
This commit is contained in:
@@ -39,6 +39,45 @@ const whatsappConfig = {
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
async function withSandbox(test: (sandboxDir: string) => Promise<void>) {
|
||||
const sandboxDir = await fs.mkdtemp(path.join(os.tmpdir(), "msg-sandbox-"));
|
||||
try {
|
||||
await test(sandboxDir);
|
||||
} finally {
|
||||
await fs.rm(sandboxDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
const runDryAction = (params: {
|
||||
cfg: OpenClawConfig;
|
||||
action: "send" | "thread-reply" | "broadcast";
|
||||
actionParams: Record<string, unknown>;
|
||||
toolContext?: Record<string, unknown>;
|
||||
abortSignal?: AbortSignal;
|
||||
sandboxRoot?: string;
|
||||
}) =>
|
||||
runMessageAction({
|
||||
cfg: params.cfg,
|
||||
action: params.action,
|
||||
params: params.actionParams as never,
|
||||
toolContext: params.toolContext as never,
|
||||
dryRun: true,
|
||||
abortSignal: params.abortSignal,
|
||||
sandboxRoot: params.sandboxRoot,
|
||||
});
|
||||
|
||||
const runDrySend = (params: {
|
||||
cfg: OpenClawConfig;
|
||||
actionParams: Record<string, unknown>;
|
||||
toolContext?: Record<string, unknown>;
|
||||
abortSignal?: AbortSignal;
|
||||
sandboxRoot?: string;
|
||||
}) =>
|
||||
runDryAction({
|
||||
...params,
|
||||
action: "send",
|
||||
});
|
||||
|
||||
describe("runMessageAction context isolation", () => {
|
||||
beforeEach(async () => {
|
||||
const { createPluginRuntime } = await import("../../plugins/runtime/index.js");
|
||||
@@ -80,62 +119,54 @@ describe("runMessageAction context isolation", () => {
|
||||
});
|
||||
|
||||
it("allows send when target matches current channel", async () => {
|
||||
const result = await runMessageAction({
|
||||
const result = await runDrySend({
|
||||
cfg: slackConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "slack",
|
||||
target: "#C12345678",
|
||||
message: "hi",
|
||||
},
|
||||
toolContext: { currentChannelId: "C12345678" },
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
});
|
||||
|
||||
it("accepts legacy to parameter for send", async () => {
|
||||
const result = await runMessageAction({
|
||||
const result = await runDrySend({
|
||||
cfg: slackConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "slack",
|
||||
to: "#C12345678",
|
||||
message: "hi",
|
||||
},
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
});
|
||||
|
||||
it("defaults to current channel when target is omitted", async () => {
|
||||
const result = await runMessageAction({
|
||||
const result = await runDrySend({
|
||||
cfg: slackConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "slack",
|
||||
message: "hi",
|
||||
},
|
||||
toolContext: { currentChannelId: "C12345678" },
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
});
|
||||
|
||||
it("allows media-only send when target matches current channel", async () => {
|
||||
const result = await runMessageAction({
|
||||
const result = await runDrySend({
|
||||
cfg: slackConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "slack",
|
||||
target: "#C12345678",
|
||||
media: "https://example.com/note.ogg",
|
||||
},
|
||||
toolContext: { currentChannelId: "C12345678" },
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
@@ -143,104 +174,92 @@ describe("runMessageAction context isolation", () => {
|
||||
|
||||
it("requires message when no media hint is provided", async () => {
|
||||
await expect(
|
||||
runMessageAction({
|
||||
runDrySend({
|
||||
cfg: slackConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "slack",
|
||||
target: "#C12345678",
|
||||
},
|
||||
toolContext: { currentChannelId: "C12345678" },
|
||||
dryRun: true,
|
||||
}),
|
||||
).rejects.toThrow(/message required/i);
|
||||
});
|
||||
|
||||
it("blocks send when target differs from current channel", async () => {
|
||||
const result = await runMessageAction({
|
||||
const result = await runDrySend({
|
||||
cfg: slackConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "slack",
|
||||
target: "channel:C99999999",
|
||||
message: "hi",
|
||||
},
|
||||
toolContext: { currentChannelId: "C12345678", currentChannelProvider: "slack" },
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
});
|
||||
|
||||
it("blocks thread-reply when channelId differs from current channel", async () => {
|
||||
const result = await runMessageAction({
|
||||
const result = await runDryAction({
|
||||
cfg: slackConfig,
|
||||
action: "thread-reply",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "slack",
|
||||
target: "C99999999",
|
||||
message: "hi",
|
||||
},
|
||||
toolContext: { currentChannelId: "C12345678", currentChannelProvider: "slack" },
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("action");
|
||||
});
|
||||
|
||||
it("allows WhatsApp send when target matches current chat", async () => {
|
||||
const result = await runMessageAction({
|
||||
const result = await runDrySend({
|
||||
cfg: whatsappConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "whatsapp",
|
||||
target: "123@g.us",
|
||||
message: "hi",
|
||||
},
|
||||
toolContext: { currentChannelId: "123@g.us" },
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
});
|
||||
|
||||
it("blocks WhatsApp send when target differs from current chat", async () => {
|
||||
const result = await runMessageAction({
|
||||
const result = await runDrySend({
|
||||
cfg: whatsappConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "whatsapp",
|
||||
target: "456@g.us",
|
||||
message: "hi",
|
||||
},
|
||||
toolContext: { currentChannelId: "123@g.us", currentChannelProvider: "whatsapp" },
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
});
|
||||
|
||||
it("allows iMessage send when target matches current handle", async () => {
|
||||
const result = await runMessageAction({
|
||||
const result = await runDrySend({
|
||||
cfg: whatsappConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "imessage",
|
||||
target: "imessage:+15551234567",
|
||||
message: "hi",
|
||||
},
|
||||
toolContext: { currentChannelId: "imessage:+15551234567" },
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
});
|
||||
|
||||
it("blocks iMessage send when target differs from current handle", async () => {
|
||||
const result = await runMessageAction({
|
||||
const result = await runDrySend({
|
||||
cfg: whatsappConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "imessage",
|
||||
target: "imessage:+15551230000",
|
||||
message: "hi",
|
||||
@@ -249,7 +268,6 @@ describe("runMessageAction context isolation", () => {
|
||||
currentChannelId: "imessage:+15551234567",
|
||||
currentChannelProvider: "imessage",
|
||||
},
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
@@ -268,14 +286,12 @@ describe("runMessageAction context isolation", () => {
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = await runMessageAction({
|
||||
const result = await runDrySend({
|
||||
cfg: multiConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
message: "hi",
|
||||
},
|
||||
toolContext: { currentChannelId: "C12345678", currentChannelProvider: "slack" },
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
@@ -284,16 +300,14 @@ describe("runMessageAction context isolation", () => {
|
||||
|
||||
it("blocks cross-provider sends by default", async () => {
|
||||
await expect(
|
||||
runMessageAction({
|
||||
runDrySend({
|
||||
cfg: slackConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "telegram",
|
||||
target: "telegram:@ops",
|
||||
message: "hi",
|
||||
},
|
||||
toolContext: { currentChannelId: "C12345678", currentChannelProvider: "slack" },
|
||||
dryRun: true,
|
||||
}),
|
||||
).rejects.toThrow(/Cross-context messaging denied/);
|
||||
});
|
||||
@@ -311,16 +325,14 @@ describe("runMessageAction context isolation", () => {
|
||||
} as OpenClawConfig;
|
||||
|
||||
await expect(
|
||||
runMessageAction({
|
||||
runDrySend({
|
||||
cfg,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "slack",
|
||||
target: "channel:C99999999",
|
||||
message: "hi",
|
||||
},
|
||||
toolContext: { currentChannelId: "C12345678", currentChannelProvider: "slack" },
|
||||
dryRun: true,
|
||||
}),
|
||||
).rejects.toThrow(/Cross-context messaging denied/);
|
||||
});
|
||||
@@ -330,15 +342,13 @@ describe("runMessageAction context isolation", () => {
|
||||
controller.abort();
|
||||
|
||||
await expect(
|
||||
runMessageAction({
|
||||
runDrySend({
|
||||
cfg: slackConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "slack",
|
||||
target: "#C12345678",
|
||||
message: "hi",
|
||||
},
|
||||
dryRun: true,
|
||||
abortSignal: controller.signal,
|
||||
}),
|
||||
).rejects.toMatchObject({ name: "AbortError" });
|
||||
@@ -349,15 +359,14 @@ describe("runMessageAction context isolation", () => {
|
||||
controller.abort();
|
||||
|
||||
await expect(
|
||||
runMessageAction({
|
||||
runDryAction({
|
||||
cfg: slackConfig,
|
||||
action: "broadcast",
|
||||
params: {
|
||||
actionParams: {
|
||||
targets: ["channel:C12345678"],
|
||||
channel: "slack",
|
||||
message: "hi",
|
||||
},
|
||||
dryRun: true,
|
||||
abortSignal: controller.signal,
|
||||
}),
|
||||
).rejects.toMatchObject({ name: "AbortError" });
|
||||
@@ -461,8 +470,7 @@ describe("runMessageAction sendAttachment hydration", () => {
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
const sandboxDir = await fs.mkdtemp(path.join(os.tmpdir(), "msg-sandbox-"));
|
||||
try {
|
||||
await withSandbox(async (sandboxDir) => {
|
||||
await runMessageAction({
|
||||
cfg,
|
||||
action: "sendAttachment",
|
||||
@@ -477,9 +485,7 @@ describe("runMessageAction sendAttachment hydration", () => {
|
||||
|
||||
const call = vi.mocked(loadWebMedia).mock.calls[0];
|
||||
expect(call?.[0]).toBe(path.join(sandboxDir, "data", "pic.png"));
|
||||
} finally {
|
||||
await fs.rm(sandboxDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -505,106 +511,84 @@ describe("runMessageAction sandboxed media validation", () => {
|
||||
});
|
||||
|
||||
it("rejects media outside the sandbox root", async () => {
|
||||
const sandboxDir = await fs.mkdtemp(path.join(os.tmpdir(), "msg-sandbox-"));
|
||||
try {
|
||||
await withSandbox(async (sandboxDir) => {
|
||||
await expect(
|
||||
runMessageAction({
|
||||
runDrySend({
|
||||
cfg: slackConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "slack",
|
||||
target: "#C12345678",
|
||||
media: "/etc/passwd",
|
||||
message: "",
|
||||
},
|
||||
sandboxRoot: sandboxDir,
|
||||
dryRun: true,
|
||||
}),
|
||||
).rejects.toThrow(/sandbox/i);
|
||||
} finally {
|
||||
await fs.rm(sandboxDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects file:// media outside the sandbox root", async () => {
|
||||
const sandboxDir = await fs.mkdtemp(path.join(os.tmpdir(), "msg-sandbox-"));
|
||||
try {
|
||||
await withSandbox(async (sandboxDir) => {
|
||||
await expect(
|
||||
runMessageAction({
|
||||
runDrySend({
|
||||
cfg: slackConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "slack",
|
||||
target: "#C12345678",
|
||||
media: "file:///etc/passwd",
|
||||
message: "",
|
||||
},
|
||||
sandboxRoot: sandboxDir,
|
||||
dryRun: true,
|
||||
}),
|
||||
).rejects.toThrow(/sandbox/i);
|
||||
} finally {
|
||||
await fs.rm(sandboxDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("rewrites sandbox-relative media paths", async () => {
|
||||
const sandboxDir = await fs.mkdtemp(path.join(os.tmpdir(), "msg-sandbox-"));
|
||||
try {
|
||||
const result = await runMessageAction({
|
||||
await withSandbox(async (sandboxDir) => {
|
||||
const result = await runDrySend({
|
||||
cfg: slackConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "slack",
|
||||
target: "#C12345678",
|
||||
media: "./data/file.txt",
|
||||
message: "",
|
||||
},
|
||||
sandboxRoot: sandboxDir,
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
expect(result.sendResult?.mediaUrl).toBe(path.join(sandboxDir, "data", "file.txt"));
|
||||
} finally {
|
||||
await fs.rm(sandboxDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("rewrites MEDIA directives under sandbox", async () => {
|
||||
const sandboxDir = await fs.mkdtemp(path.join(os.tmpdir(), "msg-sandbox-"));
|
||||
try {
|
||||
const result = await runMessageAction({
|
||||
await withSandbox(async (sandboxDir) => {
|
||||
const result = await runDrySend({
|
||||
cfg: slackConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "slack",
|
||||
target: "#C12345678",
|
||||
message: "Hello\nMEDIA: ./data/note.ogg",
|
||||
},
|
||||
sandboxRoot: sandboxDir,
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(result.kind).toBe("send");
|
||||
expect(result.sendResult?.mediaUrl).toBe(path.join(sandboxDir, "data", "note.ogg"));
|
||||
} finally {
|
||||
await fs.rm(sandboxDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects data URLs in media params", async () => {
|
||||
await expect(
|
||||
runMessageAction({
|
||||
runDrySend({
|
||||
cfg: slackConfig,
|
||||
action: "send",
|
||||
params: {
|
||||
actionParams: {
|
||||
channel: "slack",
|
||||
target: "#C12345678",
|
||||
media: "data:image/png;base64,abcd",
|
||||
message: "",
|
||||
},
|
||||
dryRun: true,
|
||||
}),
|
||||
).rejects.toThrow(/data:/i);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user