mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 12:41:23 +00:00
refactor(test): share discord send rest harness
This commit is contained in:
@@ -14,43 +14,12 @@ import {
|
||||
sendMessageDiscord,
|
||||
unpinMessageDiscord,
|
||||
} from "./send.js";
|
||||
import { makeDiscordRest } from "./send.test-harness.js";
|
||||
|
||||
vi.mock("../web/media.js", () => ({
|
||||
loadWebMedia: vi.fn().mockResolvedValue({
|
||||
buffer: Buffer.from("img"),
|
||||
fileName: "photo.jpg",
|
||||
contentType: "image/jpeg",
|
||||
kind: "image",
|
||||
}),
|
||||
loadWebMediaRaw: vi.fn().mockResolvedValue({
|
||||
buffer: Buffer.from("img"),
|
||||
fileName: "asset.png",
|
||||
contentType: "image/png",
|
||||
kind: "image",
|
||||
}),
|
||||
}));
|
||||
|
||||
const makeRest = () => {
|
||||
const postMock = vi.fn();
|
||||
const putMock = vi.fn();
|
||||
const getMock = vi.fn();
|
||||
const patchMock = vi.fn();
|
||||
const deleteMock = vi.fn();
|
||||
return {
|
||||
rest: {
|
||||
post: postMock,
|
||||
put: putMock,
|
||||
get: getMock,
|
||||
patch: patchMock,
|
||||
delete: deleteMock,
|
||||
} as unknown as import("@buape/carbon").RequestClient,
|
||||
postMock,
|
||||
putMock,
|
||||
getMock,
|
||||
patchMock,
|
||||
deleteMock,
|
||||
};
|
||||
};
|
||||
vi.mock("../web/media.js", async () => {
|
||||
const { discordWebMediaMockFactory } = await import("./send.test-harness.js");
|
||||
return discordWebMediaMockFactory();
|
||||
});
|
||||
|
||||
describe("sendMessageDiscord", () => {
|
||||
beforeEach(() => {
|
||||
@@ -58,7 +27,7 @@ describe("sendMessageDiscord", () => {
|
||||
});
|
||||
|
||||
it("sends basic channel messages", async () => {
|
||||
const { rest, postMock, getMock } = makeRest();
|
||||
const { rest, postMock, getMock } = makeDiscordRest();
|
||||
// Channel type lookup returns a normal text channel (not a forum).
|
||||
getMock.mockResolvedValueOnce({ type: ChannelType.GuildText });
|
||||
postMock.mockResolvedValue({
|
||||
@@ -77,7 +46,7 @@ describe("sendMessageDiscord", () => {
|
||||
});
|
||||
|
||||
it("auto-creates a forum thread when target is a Forum channel", async () => {
|
||||
const { rest, postMock, getMock } = makeRest();
|
||||
const { rest, postMock, getMock } = makeDiscordRest();
|
||||
// Channel type lookup returns a Forum channel.
|
||||
getMock.mockResolvedValueOnce({ type: ChannelType.GuildForum });
|
||||
postMock.mockResolvedValue({
|
||||
@@ -102,7 +71,7 @@ describe("sendMessageDiscord", () => {
|
||||
});
|
||||
|
||||
it("posts media as a follow-up message in forum channels", async () => {
|
||||
const { rest, postMock, getMock } = makeRest();
|
||||
const { rest, postMock, getMock } = makeDiscordRest();
|
||||
getMock.mockResolvedValueOnce({ type: ChannelType.GuildForum });
|
||||
postMock
|
||||
.mockResolvedValueOnce({
|
||||
@@ -138,7 +107,7 @@ describe("sendMessageDiscord", () => {
|
||||
});
|
||||
|
||||
it("chunks long forum posts into follow-up messages", async () => {
|
||||
const { rest, postMock, getMock } = makeRest();
|
||||
const { rest, postMock, getMock } = makeDiscordRest();
|
||||
getMock.mockResolvedValueOnce({ type: ChannelType.GuildForum });
|
||||
postMock
|
||||
.mockResolvedValueOnce({
|
||||
@@ -160,7 +129,7 @@ describe("sendMessageDiscord", () => {
|
||||
});
|
||||
|
||||
it("starts DM when recipient is a user", async () => {
|
||||
const { rest, postMock } = makeRest();
|
||||
const { rest, postMock } = makeDiscordRest();
|
||||
postMock
|
||||
.mockResolvedValueOnce({ id: "chan1" })
|
||||
.mockResolvedValueOnce({ id: "msg1", channel_id: "chan1" });
|
||||
@@ -182,7 +151,7 @@ describe("sendMessageDiscord", () => {
|
||||
});
|
||||
|
||||
it("rejects bare numeric IDs as ambiguous", async () => {
|
||||
const { rest } = makeRest();
|
||||
const { rest } = makeDiscordRest();
|
||||
await expect(
|
||||
sendMessageDiscord("273512430271856640", "hello", { rest, token: "t" }),
|
||||
).rejects.toThrow(/Ambiguous Discord recipient/);
|
||||
@@ -195,7 +164,7 @@ describe("sendMessageDiscord", () => {
|
||||
});
|
||||
|
||||
it("adds missing permission hints on 50013", async () => {
|
||||
const { rest, postMock, getMock } = makeRest();
|
||||
const { rest, postMock, getMock } = makeDiscordRest();
|
||||
const perms = PermissionFlagsBits.ViewChannel;
|
||||
const apiError = Object.assign(new Error("Missing Permissions"), {
|
||||
code: 50013,
|
||||
@@ -228,7 +197,7 @@ describe("sendMessageDiscord", () => {
|
||||
});
|
||||
|
||||
it("uploads media attachments", async () => {
|
||||
const { rest, postMock } = makeRest();
|
||||
const { rest, postMock } = makeDiscordRest();
|
||||
postMock.mockResolvedValue({ id: "msg", channel_id: "789" });
|
||||
const res = await sendMessageDiscord("channel:789", "photo", {
|
||||
rest,
|
||||
@@ -247,7 +216,7 @@ describe("sendMessageDiscord", () => {
|
||||
});
|
||||
|
||||
it("sends media with empty text without content field", async () => {
|
||||
const { rest, postMock } = makeRest();
|
||||
const { rest, postMock } = makeDiscordRest();
|
||||
postMock.mockResolvedValue({ id: "msg", channel_id: "789" });
|
||||
const res = await sendMessageDiscord("channel:789", "", {
|
||||
rest,
|
||||
@@ -261,7 +230,7 @@ describe("sendMessageDiscord", () => {
|
||||
});
|
||||
|
||||
it("preserves whitespace in media captions", async () => {
|
||||
const { rest, postMock } = makeRest();
|
||||
const { rest, postMock } = makeDiscordRest();
|
||||
postMock.mockResolvedValue({ id: "msg", channel_id: "789" });
|
||||
await sendMessageDiscord("channel:789", " spaced ", {
|
||||
rest,
|
||||
@@ -273,7 +242,7 @@ describe("sendMessageDiscord", () => {
|
||||
});
|
||||
|
||||
it("includes message_reference when replying", async () => {
|
||||
const { rest, postMock } = makeRest();
|
||||
const { rest, postMock } = makeDiscordRest();
|
||||
postMock.mockResolvedValue({ id: "msg1", channel_id: "789" });
|
||||
await sendMessageDiscord("channel:789", "hello", {
|
||||
rest,
|
||||
@@ -288,7 +257,7 @@ describe("sendMessageDiscord", () => {
|
||||
});
|
||||
|
||||
it("replies only on the first chunk", async () => {
|
||||
const { rest, postMock } = makeRest();
|
||||
const { rest, postMock } = makeDiscordRest();
|
||||
postMock.mockResolvedValue({ id: "msg1", channel_id: "789" });
|
||||
await sendMessageDiscord("channel:789", "a".repeat(2001), {
|
||||
rest,
|
||||
@@ -312,7 +281,7 @@ describe("reactMessageDiscord", () => {
|
||||
});
|
||||
|
||||
it("reacts with unicode emoji", async () => {
|
||||
const { rest, putMock } = makeRest();
|
||||
const { rest, putMock } = makeDiscordRest();
|
||||
await reactMessageDiscord("chan1", "msg1", "✅", { rest, token: "t" });
|
||||
expect(putMock).toHaveBeenCalledWith(
|
||||
Routes.channelMessageOwnReaction("chan1", "msg1", "%E2%9C%85"),
|
||||
@@ -320,7 +289,7 @@ describe("reactMessageDiscord", () => {
|
||||
});
|
||||
|
||||
it("normalizes variation selectors in unicode emoji", async () => {
|
||||
const { rest, putMock } = makeRest();
|
||||
const { rest, putMock } = makeDiscordRest();
|
||||
await reactMessageDiscord("chan1", "msg1", "⭐️", { rest, token: "t" });
|
||||
expect(putMock).toHaveBeenCalledWith(
|
||||
Routes.channelMessageOwnReaction("chan1", "msg1", "%E2%AD%90"),
|
||||
@@ -328,7 +297,7 @@ describe("reactMessageDiscord", () => {
|
||||
});
|
||||
|
||||
it("reacts with custom emoji syntax", async () => {
|
||||
const { rest, putMock } = makeRest();
|
||||
const { rest, putMock } = makeDiscordRest();
|
||||
await reactMessageDiscord("chan1", "msg1", "<:party_blob:123>", {
|
||||
rest,
|
||||
token: "t",
|
||||
@@ -345,7 +314,7 @@ describe("removeReactionDiscord", () => {
|
||||
});
|
||||
|
||||
it("removes a unicode emoji reaction", async () => {
|
||||
const { rest, deleteMock } = makeRest();
|
||||
const { rest, deleteMock } = makeDiscordRest();
|
||||
await removeReactionDiscord("chan1", "msg1", "✅", { rest, token: "t" });
|
||||
expect(deleteMock).toHaveBeenCalledWith(
|
||||
Routes.channelMessageOwnReaction("chan1", "msg1", "%E2%9C%85"),
|
||||
@@ -359,7 +328,7 @@ describe("removeOwnReactionsDiscord", () => {
|
||||
});
|
||||
|
||||
it("removes all own reactions on a message", async () => {
|
||||
const { rest, getMock, deleteMock } = makeRest();
|
||||
const { rest, getMock, deleteMock } = makeDiscordRest();
|
||||
getMock.mockResolvedValue({
|
||||
reactions: [
|
||||
{ emoji: { name: "✅", id: null } },
|
||||
@@ -386,7 +355,7 @@ describe("fetchReactionsDiscord", () => {
|
||||
});
|
||||
|
||||
it("returns reactions with users", async () => {
|
||||
const { rest, getMock } = makeRest();
|
||||
const { rest, getMock } = makeDiscordRest();
|
||||
getMock
|
||||
.mockResolvedValueOnce({
|
||||
reactions: [
|
||||
@@ -421,7 +390,7 @@ describe("fetchChannelPermissionsDiscord", () => {
|
||||
});
|
||||
|
||||
it("calculates permissions from guild roles", async () => {
|
||||
const { rest, getMock } = makeRest();
|
||||
const { rest, getMock } = makeDiscordRest();
|
||||
const perms = PermissionFlagsBits.ViewChannel | PermissionFlagsBits.SendMessages;
|
||||
getMock
|
||||
.mockResolvedValueOnce({
|
||||
@@ -449,7 +418,7 @@ describe("fetchChannelPermissionsDiscord", () => {
|
||||
});
|
||||
|
||||
it("treats Administrator as all permissions despite overwrites", async () => {
|
||||
const { rest, getMock } = makeRest();
|
||||
const { rest, getMock } = makeDiscordRest();
|
||||
getMock
|
||||
.mockResolvedValueOnce({
|
||||
id: "chan1",
|
||||
@@ -483,7 +452,7 @@ describe("readMessagesDiscord", () => {
|
||||
});
|
||||
|
||||
it("passes query params as an object", async () => {
|
||||
const { rest, getMock } = makeRest();
|
||||
const { rest, getMock } = makeDiscordRest();
|
||||
getMock.mockResolvedValue([]);
|
||||
await readMessagesDiscord("chan1", { limit: 5, before: "10" }, { rest, token: "t" });
|
||||
const call = getMock.mock.calls[0];
|
||||
@@ -498,7 +467,7 @@ describe("edit/delete message helpers", () => {
|
||||
});
|
||||
|
||||
it("edits message content", async () => {
|
||||
const { rest, patchMock } = makeRest();
|
||||
const { rest, patchMock } = makeDiscordRest();
|
||||
patchMock.mockResolvedValue({ id: "m1" });
|
||||
await editMessageDiscord("chan1", "m1", { content: "hello" }, { rest, token: "t" });
|
||||
expect(patchMock).toHaveBeenCalledWith(
|
||||
@@ -508,7 +477,7 @@ describe("edit/delete message helpers", () => {
|
||||
});
|
||||
|
||||
it("deletes message", async () => {
|
||||
const { rest, deleteMock } = makeRest();
|
||||
const { rest, deleteMock } = makeDiscordRest();
|
||||
deleteMock.mockResolvedValue({});
|
||||
await deleteMessageDiscord("chan1", "m1", { rest, token: "t" });
|
||||
expect(deleteMock).toHaveBeenCalledWith(Routes.channelMessage("chan1", "m1"));
|
||||
@@ -521,7 +490,7 @@ describe("pin helpers", () => {
|
||||
});
|
||||
|
||||
it("pins and unpins messages", async () => {
|
||||
const { rest, putMock, deleteMock } = makeRest();
|
||||
const { rest, putMock, deleteMock } = makeDiscordRest();
|
||||
putMock.mockResolvedValue({});
|
||||
deleteMock.mockResolvedValue({});
|
||||
await pinMessageDiscord("chan1", "m1", { rest, token: "t" });
|
||||
@@ -537,7 +506,7 @@ describe("searchMessagesDiscord", () => {
|
||||
});
|
||||
|
||||
it("uses URLSearchParams for search", async () => {
|
||||
const { rest, getMock } = makeRest();
|
||||
const { rest, getMock } = makeDiscordRest();
|
||||
getMock.mockResolvedValue({ total_results: 0, messages: [] });
|
||||
await searchMessagesDiscord(
|
||||
{ guildId: "g1", content: "hello", limit: 5 },
|
||||
@@ -548,7 +517,7 @@ describe("searchMessagesDiscord", () => {
|
||||
});
|
||||
|
||||
it("supports channel/author arrays and clamps limit", async () => {
|
||||
const { rest, getMock } = makeRest();
|
||||
const { rest, getMock } = makeDiscordRest();
|
||||
getMock.mockResolvedValue({ total_results: 0, messages: [] });
|
||||
await searchMessagesDiscord(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user