mirror of https://github.com/openclaw/openclaw.git
test(discord): cover additional utility surfaces
This commit is contained in:
parent
82508e3931
commit
59be2c8679
|
|
@ -0,0 +1,116 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
|
||||
const handleDiscordMessageActionMock = vi.hoisted(() => vi.fn(async () => ({ ok: true })));
|
||||
|
||||
vi.mock("./actions/handle-action.js", () => ({
|
||||
handleDiscordMessageAction: handleDiscordMessageActionMock,
|
||||
}));
|
||||
|
||||
import { discordMessageActions } from "./channel-actions.js";
|
||||
|
||||
describe("discordMessageActions", () => {
|
||||
it("returns no tool actions when no token-sourced Discord accounts are enabled", () => {
|
||||
const discovery = discordMessageActions.describeMessageTool?.({
|
||||
cfg: {
|
||||
channels: {
|
||||
discord: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
});
|
||||
|
||||
expect(discovery).toEqual({
|
||||
actions: [],
|
||||
capabilities: [],
|
||||
schema: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("describes enabled Discord actions for token-backed accounts", () => {
|
||||
const discovery = discordMessageActions.describeMessageTool?.({
|
||||
cfg: {
|
||||
channels: {
|
||||
discord: {
|
||||
token: "Bot token-main",
|
||||
actions: {
|
||||
polls: true,
|
||||
reactions: true,
|
||||
permissions: true,
|
||||
channels: false,
|
||||
roles: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
});
|
||||
|
||||
expect(discovery?.capabilities).toEqual(["interactive", "components"]);
|
||||
expect(discovery?.schema).not.toBeNull();
|
||||
expect(discovery?.actions).toEqual(
|
||||
expect.arrayContaining([
|
||||
"send",
|
||||
"poll",
|
||||
"react",
|
||||
"reactions",
|
||||
"emoji-list",
|
||||
"permissions",
|
||||
]),
|
||||
);
|
||||
expect(discovery?.actions).not.toContain("channel-create");
|
||||
expect(discovery?.actions).not.toContain("role-add");
|
||||
});
|
||||
|
||||
it("extracts send targets for message and thread reply actions", () => {
|
||||
expect(
|
||||
discordMessageActions.extractToolSend?.({
|
||||
args: { action: "sendMessage", to: "channel:123" },
|
||||
}),
|
||||
).toEqual({ to: "channel:123" });
|
||||
|
||||
expect(
|
||||
discordMessageActions.extractToolSend?.({
|
||||
args: { action: "threadReply", channelId: "987" },
|
||||
}),
|
||||
).toEqual({ to: "channel:987" });
|
||||
|
||||
expect(
|
||||
discordMessageActions.extractToolSend?.({
|
||||
args: { action: "threadReply", channelId: " " },
|
||||
}),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("delegates action handling to the Discord action handler", async () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
discord: {
|
||||
token: "Bot token-main",
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
const toolContext = { sessionId: "s1" };
|
||||
const mediaLocalRoots = ["/tmp/media"];
|
||||
|
||||
await discordMessageActions.handleAction?.({
|
||||
action: "send",
|
||||
params: { to: "channel:123", text: "hello" },
|
||||
cfg,
|
||||
accountId: "ops",
|
||||
requesterSenderId: "user-1",
|
||||
toolContext,
|
||||
mediaLocalRoots,
|
||||
});
|
||||
|
||||
expect(handleDiscordMessageActionMock).toHaveBeenCalledWith({
|
||||
action: "send",
|
||||
params: { to: "channel:123", text: "hello" },
|
||||
cfg,
|
||||
accountId: "ops",
|
||||
requesterSenderId: "user-1",
|
||||
toolContext,
|
||||
mediaLocalRoots,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
import { Routes } from "discord-api-types/v10";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createDiscordDraftStream } from "./draft-stream.js";
|
||||
|
||||
describe("createDiscordDraftStream", () => {
|
||||
it("holds the first preview until minInitialChars is reached", async () => {
|
||||
const rest = {
|
||||
post: vi.fn(async () => ({ id: "m1" })),
|
||||
patch: vi.fn(async () => undefined),
|
||||
delete: vi.fn(async () => undefined),
|
||||
};
|
||||
const stream = createDiscordDraftStream({
|
||||
rest: rest as never,
|
||||
channelId: "c1",
|
||||
throttleMs: 250,
|
||||
minInitialChars: 5,
|
||||
});
|
||||
|
||||
stream.update("hey");
|
||||
await stream.flush();
|
||||
|
||||
expect(rest.post).not.toHaveBeenCalled();
|
||||
expect(stream.messageId()).toBeUndefined();
|
||||
});
|
||||
|
||||
it("sends a reply preview, then edits the same message on later flushes", async () => {
|
||||
const rest = {
|
||||
post: vi.fn(async () => ({ id: "m1" })),
|
||||
patch: vi.fn(async () => undefined),
|
||||
delete: vi.fn(async () => undefined),
|
||||
};
|
||||
const stream = createDiscordDraftStream({
|
||||
rest: rest as never,
|
||||
channelId: "c1",
|
||||
throttleMs: 250,
|
||||
replyToMessageId: () => " parent-1 ",
|
||||
});
|
||||
|
||||
stream.update("first draft");
|
||||
await stream.flush();
|
||||
stream.update("second draft");
|
||||
await stream.flush();
|
||||
|
||||
expect(rest.post).toHaveBeenCalledWith(Routes.channelMessages("c1"), {
|
||||
body: {
|
||||
content: "first draft",
|
||||
message_reference: {
|
||||
message_id: "parent-1",
|
||||
fail_if_not_exists: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(rest.patch).toHaveBeenCalledWith(Routes.channelMessage("c1", "m1"), {
|
||||
body: { content: "second draft" },
|
||||
});
|
||||
expect(stream.messageId()).toBe("m1");
|
||||
});
|
||||
|
||||
it("stops previewing and warns once text exceeds the configured limit", async () => {
|
||||
const rest = {
|
||||
post: vi.fn(async () => ({ id: "m1" })),
|
||||
patch: vi.fn(async () => undefined),
|
||||
delete: vi.fn(async () => undefined),
|
||||
};
|
||||
const warn = vi.fn();
|
||||
const stream = createDiscordDraftStream({
|
||||
rest: rest as never,
|
||||
channelId: "c1",
|
||||
maxChars: 5,
|
||||
throttleMs: 250,
|
||||
warn,
|
||||
});
|
||||
|
||||
stream.update("123456");
|
||||
await stream.flush();
|
||||
|
||||
expect(rest.post).not.toHaveBeenCalled();
|
||||
expect(warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining("discord stream preview stopped"),
|
||||
);
|
||||
expect(stream.messageId()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
looksLikeDiscordTargetId,
|
||||
normalizeDiscordMessagingTarget,
|
||||
normalizeDiscordOutboundTarget,
|
||||
} from "./normalize.js";
|
||||
|
||||
describe("discord target normalization", () => {
|
||||
it("normalizes bare messaging target ids to channel targets", () => {
|
||||
expect(normalizeDiscordMessagingTarget("1234567890")).toBe("channel:1234567890");
|
||||
});
|
||||
|
||||
it("keeps explicit outbound targets and rejects missing recipients", () => {
|
||||
expect(normalizeDiscordOutboundTarget("1234567890")).toEqual({
|
||||
ok: true,
|
||||
to: "channel:1234567890",
|
||||
});
|
||||
expect(normalizeDiscordOutboundTarget("user:42")).toEqual({
|
||||
ok: true,
|
||||
to: "user:42",
|
||||
});
|
||||
|
||||
const result = normalizeDiscordOutboundTarget(" ");
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.error.message).toContain("Discord recipient is required");
|
||||
}
|
||||
});
|
||||
|
||||
it("detects Discord-style target identifiers", () => {
|
||||
expect(looksLikeDiscordTargetId("<@!123456>")).toBe(true);
|
||||
expect(looksLikeDiscordTargetId("user:123456")).toBe(true);
|
||||
expect(looksLikeDiscordTargetId("discord:123456")).toBe(true);
|
||||
expect(looksLikeDiscordTargetId("123456")).toBe(true);
|
||||
expect(looksLikeDiscordTargetId("hello world")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import type { ChannelAccountSnapshot } from "openclaw/plugin-sdk/channel-contract";
|
||||
import { collectDiscordStatusIssues } from "./status-issues.js";
|
||||
|
||||
describe("collectDiscordStatusIssues", () => {
|
||||
it("reports disabled message content intent and unresolved channel ids", () => {
|
||||
const issues = collectDiscordStatusIssues([
|
||||
{
|
||||
accountId: "ops",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
application: {
|
||||
intents: {
|
||||
messageContent: "disabled",
|
||||
},
|
||||
},
|
||||
audit: {
|
||||
unresolvedChannels: 2,
|
||||
},
|
||||
} as ChannelAccountSnapshot,
|
||||
]);
|
||||
|
||||
expect(issues).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
channel: "discord",
|
||||
accountId: "ops",
|
||||
kind: "intent",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
channel: "discord",
|
||||
accountId: "ops",
|
||||
kind: "config",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("reports channel permission failures with match metadata", () => {
|
||||
const issues = collectDiscordStatusIssues([
|
||||
{
|
||||
accountId: "ops",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
audit: {
|
||||
channels: [
|
||||
{
|
||||
channelId: "123",
|
||||
ok: false,
|
||||
missing: ["ViewChannel", "SendMessages"],
|
||||
error: "403",
|
||||
matchKey: "alerts",
|
||||
matchSource: "guilds.ops.channels",
|
||||
},
|
||||
],
|
||||
},
|
||||
} as ChannelAccountSnapshot,
|
||||
]);
|
||||
|
||||
expect(issues).toHaveLength(1);
|
||||
expect(issues[0]).toMatchObject({
|
||||
channel: "discord",
|
||||
accountId: "ops",
|
||||
kind: "permissions",
|
||||
});
|
||||
expect(issues[0]?.message).toContain("Channel 123 permission check failed");
|
||||
expect(issues[0]?.message).toContain("alerts");
|
||||
expect(issues[0]?.message).toContain("guilds.ops.channels");
|
||||
});
|
||||
|
||||
it("ignores accounts that are not enabled and configured", () => {
|
||||
expect(
|
||||
collectDiscordStatusIssues([
|
||||
{
|
||||
accountId: "ops",
|
||||
enabled: false,
|
||||
configured: true,
|
||||
} as ChannelAccountSnapshot,
|
||||
]),
|
||||
).toEqual([]);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue