




















@@ -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+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。