mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-07 22:09:57 +00:00
test: dedupe fixtures and test harness setup
This commit is contained in:
@@ -47,6 +47,22 @@ describe("bluebubblesMessageActions", () => {
|
||||
const handleAction = bluebubblesMessageActions.handleAction!;
|
||||
const callHandleAction = (ctx: Omit<Parameters<typeof handleAction>[0], "channel">) =>
|
||||
handleAction({ channel: "bluebubbles", ...ctx });
|
||||
const blueBubblesConfig = (): OpenClawConfig => ({
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
serverUrl: "http://localhost:1234",
|
||||
password: "test-password",
|
||||
},
|
||||
},
|
||||
});
|
||||
const runReactAction = async (params: Record<string, unknown>) => {
|
||||
return await callHandleAction({
|
||||
action: "react",
|
||||
params,
|
||||
cfg: blueBubblesConfig(),
|
||||
accountId: null,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -285,23 +301,10 @@ describe("bluebubblesMessageActions", () => {
|
||||
it("sends reaction successfully with chatGuid", async () => {
|
||||
const { sendBlueBubblesReaction } = await import("./reactions.js");
|
||||
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
serverUrl: "http://localhost:1234",
|
||||
password: "test-password",
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = await callHandleAction({
|
||||
action: "react",
|
||||
params: {
|
||||
emoji: "❤️",
|
||||
messageId: "msg-123",
|
||||
chatGuid: "iMessage;-;+15551234567",
|
||||
},
|
||||
cfg,
|
||||
accountId: null,
|
||||
const result = await runReactAction({
|
||||
emoji: "❤️",
|
||||
messageId: "msg-123",
|
||||
chatGuid: "iMessage;-;+15551234567",
|
||||
});
|
||||
|
||||
expect(sendBlueBubblesReaction).toHaveBeenCalledWith(
|
||||
@@ -320,24 +323,11 @@ describe("bluebubblesMessageActions", () => {
|
||||
it("sends reaction removal successfully", async () => {
|
||||
const { sendBlueBubblesReaction } = await import("./reactions.js");
|
||||
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
serverUrl: "http://localhost:1234",
|
||||
password: "test-password",
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = await callHandleAction({
|
||||
action: "react",
|
||||
params: {
|
||||
emoji: "❤️",
|
||||
messageId: "msg-123",
|
||||
chatGuid: "iMessage;-;+15551234567",
|
||||
remove: true,
|
||||
},
|
||||
cfg,
|
||||
accountId: null,
|
||||
const result = await runReactAction({
|
||||
emoji: "❤️",
|
||||
messageId: "msg-123",
|
||||
chatGuid: "iMessage;-;+15551234567",
|
||||
remove: true,
|
||||
});
|
||||
|
||||
expect(sendBlueBubblesReaction).toHaveBeenCalledWith(
|
||||
|
||||
@@ -64,6 +64,24 @@ describe("downloadBlueBubblesAttachment", () => {
|
||||
setBlueBubblesRuntime(runtimeStub);
|
||||
});
|
||||
|
||||
async function expectAttachmentTooLarge(params: { bufferBytes: number; maxBytes?: number }) {
|
||||
const largeBuffer = new Uint8Array(params.bufferBytes);
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
headers: new Headers(),
|
||||
arrayBuffer: () => Promise.resolve(largeBuffer.buffer),
|
||||
});
|
||||
|
||||
const attachment: BlueBubblesAttachment = { guid: "att-large" };
|
||||
await expect(
|
||||
downloadBlueBubblesAttachment(attachment, {
|
||||
serverUrl: "http://localhost:1234",
|
||||
password: "test",
|
||||
...(params.maxBytes === undefined ? {} : { maxBytes: params.maxBytes }),
|
||||
}),
|
||||
).rejects.toThrow("too large");
|
||||
}
|
||||
|
||||
it("throws when guid is missing", async () => {
|
||||
const attachment: BlueBubblesAttachment = {};
|
||||
await expect(
|
||||
@@ -175,38 +193,14 @@ describe("downloadBlueBubblesAttachment", () => {
|
||||
});
|
||||
|
||||
it("throws when attachment exceeds max bytes", async () => {
|
||||
const largeBuffer = new Uint8Array(10 * 1024 * 1024);
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
headers: new Headers(),
|
||||
arrayBuffer: () => Promise.resolve(largeBuffer.buffer),
|
||||
await expectAttachmentTooLarge({
|
||||
bufferBytes: 10 * 1024 * 1024,
|
||||
maxBytes: 5 * 1024 * 1024,
|
||||
});
|
||||
|
||||
const attachment: BlueBubblesAttachment = { guid: "att-large" };
|
||||
await expect(
|
||||
downloadBlueBubblesAttachment(attachment, {
|
||||
serverUrl: "http://localhost:1234",
|
||||
password: "test",
|
||||
maxBytes: 5 * 1024 * 1024,
|
||||
}),
|
||||
).rejects.toThrow("too large");
|
||||
});
|
||||
|
||||
it("uses default max bytes when not specified", async () => {
|
||||
const largeBuffer = new Uint8Array(9 * 1024 * 1024);
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
headers: new Headers(),
|
||||
arrayBuffer: () => Promise.resolve(largeBuffer.buffer),
|
||||
});
|
||||
|
||||
const attachment: BlueBubblesAttachment = { guid: "att-large" };
|
||||
await expect(
|
||||
downloadBlueBubblesAttachment(attachment, {
|
||||
serverUrl: "http://localhost:1234",
|
||||
password: "test",
|
||||
}),
|
||||
).rejects.toThrow("too large");
|
||||
await expectAttachmentTooLarge({ bufferBytes: 9 * 1024 * 1024 });
|
||||
});
|
||||
|
||||
it("uses attachment mimeType as fallback when response has no content-type", async () => {
|
||||
|
||||
@@ -22,6 +22,44 @@ installBlueBubblesFetchTestHooks({
|
||||
});
|
||||
|
||||
describe("chat", () => {
|
||||
function mockOkTextResponse() {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: () => Promise.resolve(""),
|
||||
});
|
||||
}
|
||||
|
||||
async function expectCalledUrlIncludesPassword(params: {
|
||||
password: string;
|
||||
invoke: () => Promise<void>;
|
||||
}) {
|
||||
mockOkTextResponse();
|
||||
await params.invoke();
|
||||
const calledUrl = mockFetch.mock.calls[0][0] as string;
|
||||
expect(calledUrl).toContain(`password=${params.password}`);
|
||||
}
|
||||
|
||||
async function expectCalledUrlUsesConfigCredentials(params: {
|
||||
serverHost: string;
|
||||
password: string;
|
||||
invoke: (cfg: {
|
||||
channels: { bluebubbles: { serverUrl: string; password: string } };
|
||||
}) => Promise<void>;
|
||||
}) {
|
||||
mockOkTextResponse();
|
||||
await params.invoke({
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
serverUrl: `http://${params.serverHost}`,
|
||||
password: params.password,
|
||||
},
|
||||
},
|
||||
});
|
||||
const calledUrl = mockFetch.mock.calls[0][0] as string;
|
||||
expect(calledUrl).toContain(params.serverHost);
|
||||
expect(calledUrl).toContain(`password=${params.password}`);
|
||||
}
|
||||
|
||||
describe("markBlueBubblesChatRead", () => {
|
||||
it("does nothing when chatGuid is empty or whitespace", async () => {
|
||||
for (const chatGuid of ["", " "]) {
|
||||
@@ -73,18 +111,14 @@ describe("chat", () => {
|
||||
});
|
||||
|
||||
it("includes password in URL query", async () => {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: () => Promise.resolve(""),
|
||||
});
|
||||
|
||||
await markBlueBubblesChatRead("chat-123", {
|
||||
serverUrl: "http://localhost:1234",
|
||||
await expectCalledUrlIncludesPassword({
|
||||
password: "my-secret",
|
||||
invoke: () =>
|
||||
markBlueBubblesChatRead("chat-123", {
|
||||
serverUrl: "http://localhost:1234",
|
||||
password: "my-secret",
|
||||
}),
|
||||
});
|
||||
|
||||
const calledUrl = mockFetch.mock.calls[0][0] as string;
|
||||
expect(calledUrl).toContain("password=my-secret");
|
||||
});
|
||||
|
||||
it("throws on non-ok response", async () => {
|
||||
@@ -119,25 +153,14 @@ describe("chat", () => {
|
||||
});
|
||||
|
||||
it("resolves credentials from config", async () => {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: () => Promise.resolve(""),
|
||||
await expectCalledUrlUsesConfigCredentials({
|
||||
serverHost: "config-server:9999",
|
||||
password: "config-pass",
|
||||
invoke: (cfg) =>
|
||||
markBlueBubblesChatRead("chat-123", {
|
||||
cfg,
|
||||
}),
|
||||
});
|
||||
|
||||
await markBlueBubblesChatRead("chat-123", {
|
||||
cfg: {
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
serverUrl: "http://config-server:9999",
|
||||
password: "config-pass",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const calledUrl = mockFetch.mock.calls[0][0] as string;
|
||||
expect(calledUrl).toContain("config-server:9999");
|
||||
expect(calledUrl).toContain("password=config-pass");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -536,18 +559,14 @@ describe("chat", () => {
|
||||
});
|
||||
|
||||
it("includes password in URL query", async () => {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: () => Promise.resolve(""),
|
||||
});
|
||||
|
||||
await setGroupIconBlueBubbles("chat-123", new Uint8Array([1, 2, 3]), "icon.png", {
|
||||
serverUrl: "http://localhost:1234",
|
||||
await expectCalledUrlIncludesPassword({
|
||||
password: "my-secret",
|
||||
invoke: () =>
|
||||
setGroupIconBlueBubbles("chat-123", new Uint8Array([1, 2, 3]), "icon.png", {
|
||||
serverUrl: "http://localhost:1234",
|
||||
password: "my-secret",
|
||||
}),
|
||||
});
|
||||
|
||||
const calledUrl = mockFetch.mock.calls[0][0] as string;
|
||||
expect(calledUrl).toContain("password=my-secret");
|
||||
});
|
||||
|
||||
it("throws on non-ok response", async () => {
|
||||
@@ -582,25 +601,14 @@ describe("chat", () => {
|
||||
});
|
||||
|
||||
it("resolves credentials from config", async () => {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: () => Promise.resolve(""),
|
||||
await expectCalledUrlUsesConfigCredentials({
|
||||
serverHost: "config-server:9999",
|
||||
password: "config-pass",
|
||||
invoke: (cfg) =>
|
||||
setGroupIconBlueBubbles("chat-123", new Uint8Array([1]), "icon.png", {
|
||||
cfg,
|
||||
}),
|
||||
});
|
||||
|
||||
await setGroupIconBlueBubbles("chat-123", new Uint8Array([1]), "icon.png", {
|
||||
cfg: {
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
serverUrl: "http://config-server:9999",
|
||||
password: "config-pass",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const calledUrl = mockFetch.mock.calls[0][0] as string;
|
||||
expect(calledUrl).toContain("config-server:9999");
|
||||
expect(calledUrl).toContain("password=config-pass");
|
||||
});
|
||||
|
||||
it("includes filename in multipart body", async () => {
|
||||
|
||||
@@ -19,6 +19,27 @@ describe("reactions", () => {
|
||||
});
|
||||
|
||||
describe("sendBlueBubblesReaction", () => {
|
||||
async function expectRemovedReaction(emoji: string) {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: () => Promise.resolve(""),
|
||||
});
|
||||
|
||||
await sendBlueBubblesReaction({
|
||||
chatGuid: "chat-123",
|
||||
messageGuid: "msg-123",
|
||||
emoji,
|
||||
remove: true,
|
||||
opts: {
|
||||
serverUrl: "http://localhost:1234",
|
||||
password: "test",
|
||||
},
|
||||
});
|
||||
|
||||
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
||||
expect(body.reaction).toBe("-love");
|
||||
}
|
||||
|
||||
it("throws when chatGuid is empty", async () => {
|
||||
await expect(
|
||||
sendBlueBubblesReaction({
|
||||
@@ -208,45 +229,11 @@ describe("reactions", () => {
|
||||
});
|
||||
|
||||
it("sends reaction removal with dash prefix", async () => {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: () => Promise.resolve(""),
|
||||
});
|
||||
|
||||
await sendBlueBubblesReaction({
|
||||
chatGuid: "chat-123",
|
||||
messageGuid: "msg-123",
|
||||
emoji: "love",
|
||||
remove: true,
|
||||
opts: {
|
||||
serverUrl: "http://localhost:1234",
|
||||
password: "test",
|
||||
},
|
||||
});
|
||||
|
||||
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
||||
expect(body.reaction).toBe("-love");
|
||||
await expectRemovedReaction("love");
|
||||
});
|
||||
|
||||
it("strips leading dash from emoji when remove flag is set", async () => {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: () => Promise.resolve(""),
|
||||
});
|
||||
|
||||
await sendBlueBubblesReaction({
|
||||
chatGuid: "chat-123",
|
||||
messageGuid: "msg-123",
|
||||
emoji: "-love",
|
||||
remove: true,
|
||||
opts: {
|
||||
serverUrl: "http://localhost:1234",
|
||||
password: "test",
|
||||
},
|
||||
});
|
||||
|
||||
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
||||
expect(body.reaction).toBe("-love");
|
||||
await expectRemovedReaction("-love");
|
||||
});
|
||||
|
||||
it("uses custom partIndex when provided", async () => {
|
||||
|
||||
@@ -44,6 +44,23 @@ function mockSendResponse(body: unknown) {
|
||||
});
|
||||
}
|
||||
|
||||
function mockNewChatSendResponse(guid: string) {
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ data: [] }),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: () =>
|
||||
Promise.resolve(
|
||||
JSON.stringify({
|
||||
data: { guid },
|
||||
}),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
describe("send", () => {
|
||||
describe("resolveChatGuidForTarget", () => {
|
||||
const resolveHandleTargetGuid = async (data: Array<Record<string, unknown>>) => {
|
||||
@@ -453,20 +470,7 @@ describe("send", () => {
|
||||
});
|
||||
|
||||
it("strips markdown when creating a new chat", async () => {
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ data: [] }),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: () =>
|
||||
Promise.resolve(
|
||||
JSON.stringify({
|
||||
data: { guid: "new-msg-stripped" },
|
||||
}),
|
||||
),
|
||||
});
|
||||
mockNewChatSendResponse("new-msg-stripped");
|
||||
|
||||
const result = await sendMessageBlueBubbles("+15550009999", "**Welcome** to the _chat_!", {
|
||||
serverUrl: "http://localhost:1234",
|
||||
@@ -483,20 +487,7 @@ describe("send", () => {
|
||||
});
|
||||
|
||||
it("creates a new chat when handle target is missing", async () => {
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ data: [] }),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
text: () =>
|
||||
Promise.resolve(
|
||||
JSON.stringify({
|
||||
data: { guid: "new-msg-guid" },
|
||||
}),
|
||||
),
|
||||
});
|
||||
mockNewChatSendResponse("new-msg-guid");
|
||||
|
||||
const result = await sendMessageBlueBubbles("+15550009999", "Hello new chat", {
|
||||
serverUrl: "http://localhost:1234",
|
||||
|
||||
Reference in New Issue
Block a user