mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 13:20:23 +00:00
refactor(tests): share outbound runner and delivery helpers
This commit is contained in:
@@ -45,6 +45,28 @@ vi.mock("./delivery-queue.js", () => ({
|
||||
|
||||
const { deliverOutboundPayloads, normalizeOutboundPayloads } = await import("./deliver.js");
|
||||
|
||||
const telegramChunkConfig: OpenClawConfig = {
|
||||
channels: { telegram: { botToken: "tok-1", textChunkLimit: 2 } },
|
||||
};
|
||||
|
||||
const whatsappChunkConfig: OpenClawConfig = {
|
||||
channels: { whatsapp: { textChunkLimit: 4000 } },
|
||||
};
|
||||
|
||||
async function deliverWhatsAppPayload(params: {
|
||||
sendWhatsApp: ReturnType<typeof vi.fn>;
|
||||
payload: { text: string; mediaUrl?: string };
|
||||
cfg?: OpenClawConfig;
|
||||
}) {
|
||||
return deliverOutboundPayloads({
|
||||
cfg: params.cfg ?? whatsappChunkConfig,
|
||||
channel: "whatsapp",
|
||||
to: "+1555",
|
||||
payloads: [params.payload],
|
||||
deps: { sendWhatsApp: params.sendWhatsApp },
|
||||
});
|
||||
}
|
||||
|
||||
describe("deliverOutboundPayloads", () => {
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(defaultRegistry);
|
||||
@@ -65,14 +87,11 @@ describe("deliverOutboundPayloads", () => {
|
||||
});
|
||||
it("chunks telegram markdown and passes through accountId", async () => {
|
||||
const sendTelegram = vi.fn().mockResolvedValue({ messageId: "m1", chatId: "c1" });
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: { telegram: { botToken: "tok-1", textChunkLimit: 2 } },
|
||||
};
|
||||
const prevTelegramToken = process.env.TELEGRAM_BOT_TOKEN;
|
||||
process.env.TELEGRAM_BOT_TOKEN = "";
|
||||
try {
|
||||
const results = await deliverOutboundPayloads({
|
||||
cfg,
|
||||
cfg: telegramChunkConfig,
|
||||
channel: "telegram",
|
||||
to: "123",
|
||||
payloads: [{ text: "abcd" }],
|
||||
@@ -98,12 +117,9 @@ describe("deliverOutboundPayloads", () => {
|
||||
|
||||
it("passes explicit accountId to sendTelegram", async () => {
|
||||
const sendTelegram = vi.fn().mockResolvedValue({ messageId: "m1", chatId: "c1" });
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: { telegram: { botToken: "tok-1", textChunkLimit: 2 } },
|
||||
};
|
||||
|
||||
await deliverOutboundPayloads({
|
||||
cfg,
|
||||
cfg: telegramChunkConfig,
|
||||
channel: "telegram",
|
||||
to: "123",
|
||||
accountId: "default",
|
||||
@@ -120,12 +136,9 @@ describe("deliverOutboundPayloads", () => {
|
||||
|
||||
it("scopes media local roots to the active agent workspace when agentId is provided", async () => {
|
||||
const sendTelegram = vi.fn().mockResolvedValue({ messageId: "m1", chatId: "c1" });
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: { telegram: { botToken: "tok-1", textChunkLimit: 2 } },
|
||||
};
|
||||
|
||||
await deliverOutboundPayloads({
|
||||
cfg,
|
||||
cfg: telegramChunkConfig,
|
||||
channel: "telegram",
|
||||
to: "123",
|
||||
agentId: "work",
|
||||
@@ -251,16 +264,9 @@ describe("deliverOutboundPayloads", () => {
|
||||
|
||||
it("strips leading blank lines for WhatsApp text payloads", async () => {
|
||||
const sendWhatsApp = vi.fn().mockResolvedValue({ messageId: "w1", toJid: "jid" });
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: { whatsapp: { textChunkLimit: 4000 } },
|
||||
};
|
||||
|
||||
await deliverOutboundPayloads({
|
||||
cfg,
|
||||
channel: "whatsapp",
|
||||
to: "+1555",
|
||||
payloads: [{ text: "\n\nHello from WhatsApp" }],
|
||||
deps: { sendWhatsApp },
|
||||
await deliverWhatsAppPayload({
|
||||
sendWhatsApp,
|
||||
payload: { text: "\n\nHello from WhatsApp" },
|
||||
});
|
||||
|
||||
expect(sendWhatsApp).toHaveBeenCalledTimes(1);
|
||||
@@ -274,16 +280,9 @@ describe("deliverOutboundPayloads", () => {
|
||||
|
||||
it("drops whitespace-only WhatsApp text payloads when no media is attached", async () => {
|
||||
const sendWhatsApp = vi.fn().mockResolvedValue({ messageId: "w1", toJid: "jid" });
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: { whatsapp: { textChunkLimit: 4000 } },
|
||||
};
|
||||
|
||||
const results = await deliverOutboundPayloads({
|
||||
cfg,
|
||||
channel: "whatsapp",
|
||||
to: "+1555",
|
||||
payloads: [{ text: " \n\t " }],
|
||||
deps: { sendWhatsApp },
|
||||
const results = await deliverWhatsAppPayload({
|
||||
sendWhatsApp,
|
||||
payload: { text: " \n\t " },
|
||||
});
|
||||
|
||||
expect(sendWhatsApp).not.toHaveBeenCalled();
|
||||
@@ -292,16 +291,9 @@ describe("deliverOutboundPayloads", () => {
|
||||
|
||||
it("keeps WhatsApp media payloads but clears whitespace-only captions", async () => {
|
||||
const sendWhatsApp = vi.fn().mockResolvedValue({ messageId: "w1", toJid: "jid" });
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: { whatsapp: { textChunkLimit: 4000 } },
|
||||
};
|
||||
|
||||
await deliverOutboundPayloads({
|
||||
cfg,
|
||||
channel: "whatsapp",
|
||||
to: "+1555",
|
||||
payloads: [{ text: " \n\t ", mediaUrl: "https://example.com/photo.png" }],
|
||||
deps: { sendWhatsApp },
|
||||
await deliverWhatsAppPayload({
|
||||
sendWhatsApp,
|
||||
payload: { text: " \n\t ", mediaUrl: "https://example.com/photo.png" },
|
||||
});
|
||||
|
||||
expect(sendWhatsApp).toHaveBeenCalledTimes(1);
|
||||
@@ -504,13 +496,10 @@ describe("deliverOutboundPayloads", () => {
|
||||
|
||||
it("mirrors delivered output when mirror options are provided", async () => {
|
||||
const sendTelegram = vi.fn().mockResolvedValue({ messageId: "m1", chatId: "c1" });
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: { telegram: { botToken: "tok-1", textChunkLimit: 2 } },
|
||||
};
|
||||
mocks.appendAssistantMessageToSessionTranscript.mockClear();
|
||||
|
||||
await deliverOutboundPayloads({
|
||||
cfg,
|
||||
cfg: telegramChunkConfig,
|
||||
channel: "telegram",
|
||||
to: "123",
|
||||
payloads: [{ text: "caption", mediaUrl: "https://example.com/files/report.pdf?sig=1" }],
|
||||
|
||||
@@ -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