Skip to content

Commit 05eda57

Browse files
committed
refactor: migrate bundled plugins to message lifecycle
1 parent 2ead150 commit 05eda57

223 files changed

Lines changed: 8569 additions & 1355 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import {
2+
createMessageReceiptFromOutboundResults,
3+
verifyChannelMessageAdapterCapabilityProofs,
4+
} from "openclaw/plugin-sdk/channel-message";
5+
import { describe, expect, it, vi } from "vitest";
6+
import { bluebubblesPlugin } from "./channel.js";
7+
8+
const sendMessageBlueBubblesMock = vi.hoisted(() => vi.fn());
9+
const sendBlueBubblesMediaMock = vi.hoisted(() => vi.fn());
10+
const resolveBlueBubblesMessageIdMock = vi.hoisted(() => vi.fn());
11+
12+
vi.mock("./channel.runtime.js", () => ({
13+
blueBubblesChannelRuntime: {
14+
sendMessageBlueBubbles: sendMessageBlueBubblesMock,
15+
sendBlueBubblesMedia: sendBlueBubblesMediaMock,
16+
resolveBlueBubblesMessageId: resolveBlueBubblesMessageIdMock,
17+
},
18+
}));
19+
20+
describe("bluebubbles message adapter", () => {
21+
it("declares durable text, media, and reply target capabilities with receipt proofs", async () => {
22+
sendMessageBlueBubblesMock.mockImplementation(
23+
async (_to: string, _text: string, opts: { replyToMessageGuid?: string } = {}) => ({
24+
messageId: opts.replyToMessageGuid ? "bb-reply-1" : "bb-text-1",
25+
receipt: createMessageReceiptFromOutboundResults({
26+
results: [
27+
{
28+
channel: "bluebubbles",
29+
messageId: opts.replyToMessageGuid ? "bb-reply-1" : "bb-text-1",
30+
},
31+
],
32+
kind: "text",
33+
...(opts.replyToMessageGuid ? { replyToId: opts.replyToMessageGuid } : {}),
34+
}),
35+
}),
36+
);
37+
sendBlueBubblesMediaMock.mockResolvedValue({
38+
messageId: "bb-media-1",
39+
receipt: createMessageReceiptFromOutboundResults({
40+
results: [{ channel: "bluebubbles", messageId: "bb-media-1" }],
41+
kind: "media",
42+
}),
43+
});
44+
resolveBlueBubblesMessageIdMock.mockReturnValue("guid-reply-1");
45+
46+
await expect(
47+
verifyChannelMessageAdapterCapabilityProofs({
48+
adapterName: "bluebubbles",
49+
adapter: bluebubblesPlugin.message!,
50+
proofs: {
51+
text: async () => {
52+
const result = await bluebubblesPlugin.message?.send?.text?.({
53+
cfg: {},
54+
to: "+15551234567",
55+
text: "hello",
56+
});
57+
expect(sendMessageBlueBubblesMock).toHaveBeenCalledWith("+15551234567", "hello", {
58+
cfg: {},
59+
accountId: undefined,
60+
replyToMessageGuid: undefined,
61+
});
62+
expect(result?.receipt.platformMessageIds).toEqual(["bb-text-1"]);
63+
},
64+
media: async () => {
65+
const result = await bluebubblesPlugin.message?.send?.media?.({
66+
cfg: {},
67+
to: "+15551234567",
68+
text: "image",
69+
mediaUrl: "https://example.com/image.png",
70+
});
71+
expect(sendBlueBubblesMediaMock).toHaveBeenCalledWith(
72+
expect.objectContaining({
73+
to: "+15551234567",
74+
mediaUrl: "https://example.com/image.png",
75+
caption: "image",
76+
}),
77+
);
78+
expect(result?.receipt.platformMessageIds).toEqual(["bb-media-1"]);
79+
},
80+
replyTo: async () => {
81+
const result = await bluebubblesPlugin.message?.send?.text?.({
82+
cfg: {},
83+
to: "chat_guid:chat-1",
84+
text: "reply",
85+
replyToId: "short-1",
86+
});
87+
expect(resolveBlueBubblesMessageIdMock).toHaveBeenCalledWith(
88+
"short-1",
89+
expect.objectContaining({ requireKnownShortId: true }),
90+
);
91+
expect(sendMessageBlueBubblesMock).toHaveBeenCalledWith("chat_guid:chat-1", "reply", {
92+
cfg: {},
93+
accountId: undefined,
94+
replyToMessageGuid: "guid-reply-1",
95+
});
96+
expect(result?.receipt.replyToId).toBe("guid-reply-1");
97+
},
98+
messageSendingHooks: async () => {
99+
const beforeSendAttempt = vi.fn(() => "pending-1");
100+
const afterSendFailure = vi.fn();
101+
const ctx = {
102+
cfg: {},
103+
kind: "text" as const,
104+
to: "+15551234567",
105+
text: "hello",
106+
deps: {
107+
bluebubblesMessageLifecycle: {
108+
beforeSendAttempt,
109+
afterSendFailure,
110+
},
111+
},
112+
};
113+
const attemptToken =
114+
await bluebubblesPlugin.message?.send?.lifecycle?.beforeSendAttempt?.(ctx);
115+
await bluebubblesPlugin.message?.send?.lifecycle?.afterSendFailure?.({
116+
...ctx,
117+
error: new Error("send failed"),
118+
attemptToken,
119+
});
120+
expect(beforeSendAttempt).toHaveBeenCalledWith(ctx);
121+
expect(afterSendFailure).toHaveBeenCalledWith(
122+
expect.objectContaining({
123+
kind: "text",
124+
attemptToken: "pending-1",
125+
error: expect.any(Error),
126+
}),
127+
);
128+
},
129+
afterSendSuccess: async () => {
130+
const beforeSendAttempt = vi.fn(() => "pending-1");
131+
const afterSendSuccess = vi.fn();
132+
const ctx = {
133+
cfg: {},
134+
kind: "text" as const,
135+
to: "+15551234567",
136+
text: "hello",
137+
deps: {
138+
bluebubblesMessageLifecycle: {
139+
beforeSendAttempt,
140+
afterSendSuccess,
141+
},
142+
},
143+
};
144+
const attemptToken =
145+
await bluebubblesPlugin.message?.send?.lifecycle?.beforeSendAttempt?.(ctx);
146+
await bluebubblesPlugin.message?.send?.lifecycle?.afterSendSuccess?.({
147+
...ctx,
148+
result: {
149+
messageId: "bb-text-1",
150+
receipt: createMessageReceiptFromOutboundResults({
151+
results: [{ channel: "bluebubbles", messageId: "bb-text-1" }],
152+
kind: "text",
153+
}),
154+
},
155+
attemptToken,
156+
});
157+
expect(beforeSendAttempt).toHaveBeenCalledWith(ctx);
158+
expect(afterSendSuccess).toHaveBeenCalledWith(
159+
expect.objectContaining({
160+
kind: "text",
161+
attemptToken: "pending-1",
162+
result: expect.objectContaining({ messageId: "bb-text-1" }),
163+
}),
164+
);
165+
},
166+
},
167+
}),
168+
).resolves.toEqual(
169+
expect.arrayContaining([
170+
{ capability: "text", status: "verified" },
171+
{ capability: "media", status: "verified" },
172+
{ capability: "replyTo", status: "verified" },
173+
{ capability: "messageSendingHooks", status: "verified" },
174+
{ capability: "afterSendSuccess", status: "verified" },
175+
]),
176+
);
177+
});
178+
});

0 commit comments

Comments
 (0)