@auxiora/channels 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +191 -0
- package/dist/adapters/bluebubbles.d.ts +63 -0
- package/dist/adapters/bluebubbles.d.ts.map +1 -0
- package/dist/adapters/bluebubbles.js +197 -0
- package/dist/adapters/bluebubbles.js.map +1 -0
- package/dist/adapters/discord.d.ts +27 -0
- package/dist/adapters/discord.d.ts.map +1 -0
- package/dist/adapters/discord.js +202 -0
- package/dist/adapters/discord.js.map +1 -0
- package/dist/adapters/email.d.ts +39 -0
- package/dist/adapters/email.d.ts.map +1 -0
- package/dist/adapters/email.js +359 -0
- package/dist/adapters/email.js.map +1 -0
- package/dist/adapters/googlechat.d.ts +77 -0
- package/dist/adapters/googlechat.d.ts.map +1 -0
- package/dist/adapters/googlechat.js +232 -0
- package/dist/adapters/googlechat.js.map +1 -0
- package/dist/adapters/matrix.d.ts +37 -0
- package/dist/adapters/matrix.d.ts.map +1 -0
- package/dist/adapters/matrix.js +262 -0
- package/dist/adapters/matrix.js.map +1 -0
- package/dist/adapters/signal.d.ts +32 -0
- package/dist/adapters/signal.d.ts.map +1 -0
- package/dist/adapters/signal.js +216 -0
- package/dist/adapters/signal.js.map +1 -0
- package/dist/adapters/slack.d.ts +29 -0
- package/dist/adapters/slack.d.ts.map +1 -0
- package/dist/adapters/slack.js +202 -0
- package/dist/adapters/slack.js.map +1 -0
- package/dist/adapters/teams.d.ts +66 -0
- package/dist/adapters/teams.d.ts.map +1 -0
- package/dist/adapters/teams.js +227 -0
- package/dist/adapters/teams.js.map +1 -0
- package/dist/adapters/telegram.d.ts +28 -0
- package/dist/adapters/telegram.d.ts.map +1 -0
- package/dist/adapters/telegram.js +170 -0
- package/dist/adapters/telegram.js.map +1 -0
- package/dist/adapters/twilio.d.ts +63 -0
- package/dist/adapters/twilio.d.ts.map +1 -0
- package/dist/adapters/twilio.js +193 -0
- package/dist/adapters/twilio.js.map +1 -0
- package/dist/adapters/whatsapp.d.ts +99 -0
- package/dist/adapters/whatsapp.d.ts.map +1 -0
- package/dist/adapters/whatsapp.js +218 -0
- package/dist/adapters/whatsapp.js.map +1 -0
- package/dist/adapters/zalo.d.ts +64 -0
- package/dist/adapters/zalo.d.ts.map +1 -0
- package/dist/adapters/zalo.js +216 -0
- package/dist/adapters/zalo.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/manager.d.ts +35 -0
- package/dist/manager.d.ts.map +1 -0
- package/dist/manager.js +127 -0
- package/dist/manager.js.map +1 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +32 -0
- package/src/adapters/bluebubbles.ts +294 -0
- package/src/adapters/discord.ts +253 -0
- package/src/adapters/email.ts +457 -0
- package/src/adapters/googlechat.ts +364 -0
- package/src/adapters/matrix.ts +376 -0
- package/src/adapters/signal.ts +313 -0
- package/src/adapters/slack.ts +252 -0
- package/src/adapters/teams.ts +320 -0
- package/src/adapters/telegram.ts +208 -0
- package/src/adapters/twilio.ts +256 -0
- package/src/adapters/whatsapp.ts +342 -0
- package/src/adapters/zalo.ts +319 -0
- package/src/index.ts +78 -0
- package/src/manager.ts +180 -0
- package/src/types.ts +84 -0
- package/tests/bluebubbles.test.ts +438 -0
- package/tests/email.test.ts +136 -0
- package/tests/googlechat.test.ts +439 -0
- package/tests/matrix.test.ts +564 -0
- package/tests/signal.test.ts +404 -0
- package/tests/slack.test.ts +343 -0
- package/tests/teams.test.ts +429 -0
- package/tests/twilio.test.ts +269 -0
- package/tests/whatsapp.test.ts +530 -0
- package/tests/zalo.test.ts +499 -0
- package/tsconfig.json +8 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { WhatsAppAdapter } from '../src/adapters/whatsapp.js';
|
|
3
|
+
|
|
4
|
+
// Mock audit
|
|
5
|
+
vi.mock('@auxiora/audit', () => ({
|
|
6
|
+
audit: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
describe('WhatsAppAdapter', () => {
|
|
10
|
+
let adapter: WhatsAppAdapter;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
adapter = new WhatsAppAdapter({
|
|
14
|
+
phoneNumberId: '123456789',
|
|
15
|
+
accessToken: 'test-access-token',
|
|
16
|
+
verifyToken: 'test-verify-token',
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
vi.restoreAllMocks();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should have correct metadata', () => {
|
|
25
|
+
expect(adapter.type).toBe('whatsapp');
|
|
26
|
+
expect(adapter.name).toBe('WhatsApp');
|
|
27
|
+
expect(adapter.isConnected()).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should connect successfully', async () => {
|
|
31
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
|
|
32
|
+
ok: true,
|
|
33
|
+
json: async () => ({ id: '123456789', display_phone_number: '+1234567890' }),
|
|
34
|
+
} as Response);
|
|
35
|
+
|
|
36
|
+
await adapter.connect();
|
|
37
|
+
expect(adapter.isConnected()).toBe(true);
|
|
38
|
+
|
|
39
|
+
await adapter.disconnect();
|
|
40
|
+
expect(adapter.isConnected()).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should fail to connect with invalid credentials', async () => {
|
|
44
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
|
|
45
|
+
ok: false,
|
|
46
|
+
status: 401,
|
|
47
|
+
statusText: 'Unauthorized',
|
|
48
|
+
} as Response);
|
|
49
|
+
|
|
50
|
+
await expect(adapter.connect()).rejects.toThrow('Failed to verify WhatsApp credentials');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should verify webhook subscription', () => {
|
|
54
|
+
const challenge = adapter.verifyWebhook('subscribe', 'test-verify-token', 'challenge-123');
|
|
55
|
+
expect(challenge).toBe('challenge-123');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should reject invalid webhook verification', () => {
|
|
59
|
+
const challenge = adapter.verifyWebhook('subscribe', 'wrong-token', 'challenge-123');
|
|
60
|
+
expect(challenge).toBeNull();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should reject non-subscribe mode', () => {
|
|
64
|
+
const challenge = adapter.verifyWebhook('unsubscribe', 'test-verify-token', 'challenge-123');
|
|
65
|
+
expect(challenge).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should handle incoming text message', async () => {
|
|
69
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
|
|
70
|
+
ok: true,
|
|
71
|
+
json: async () => ({ id: '123456789' }),
|
|
72
|
+
} as Response);
|
|
73
|
+
await adapter.connect();
|
|
74
|
+
|
|
75
|
+
const receivedMessages: unknown[] = [];
|
|
76
|
+
adapter.onMessage(async (msg) => {
|
|
77
|
+
receivedMessages.push(msg);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await adapter.handleWebhook({
|
|
81
|
+
object: 'whatsapp_business_account',
|
|
82
|
+
entry: [
|
|
83
|
+
{
|
|
84
|
+
id: 'entry-1',
|
|
85
|
+
changes: [
|
|
86
|
+
{
|
|
87
|
+
value: {
|
|
88
|
+
messaging_product: 'whatsapp',
|
|
89
|
+
metadata: {
|
|
90
|
+
display_phone_number: '+1234567890',
|
|
91
|
+
phone_number_id: '123456789',
|
|
92
|
+
},
|
|
93
|
+
contacts: [
|
|
94
|
+
{
|
|
95
|
+
profile: { name: 'Alice' },
|
|
96
|
+
wa_id: '0987654321',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
messages: [
|
|
100
|
+
{
|
|
101
|
+
from: '0987654321',
|
|
102
|
+
id: 'wamid.abc123',
|
|
103
|
+
timestamp: '1700000000',
|
|
104
|
+
type: 'text',
|
|
105
|
+
text: { body: 'Hello WhatsApp!' },
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
field: 'messages',
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(receivedMessages).toHaveLength(1);
|
|
117
|
+
const msg = receivedMessages[0] as {
|
|
118
|
+
content: string;
|
|
119
|
+
senderId: string;
|
|
120
|
+
senderName: string;
|
|
121
|
+
channelId: string;
|
|
122
|
+
};
|
|
123
|
+
expect(msg.content).toBe('Hello WhatsApp!');
|
|
124
|
+
expect(msg.senderId).toBe('0987654321');
|
|
125
|
+
expect(msg.senderName).toBe('Alice');
|
|
126
|
+
expect(msg.channelId).toBe('0987654321');
|
|
127
|
+
|
|
128
|
+
await adapter.disconnect();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should handle incoming image message', async () => {
|
|
132
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
|
|
133
|
+
ok: true,
|
|
134
|
+
json: async () => ({ id: '123456789' }),
|
|
135
|
+
} as Response);
|
|
136
|
+
await adapter.connect();
|
|
137
|
+
|
|
138
|
+
const receivedMessages: unknown[] = [];
|
|
139
|
+
adapter.onMessage(async (msg) => {
|
|
140
|
+
receivedMessages.push(msg);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await adapter.handleWebhook({
|
|
144
|
+
object: 'whatsapp_business_account',
|
|
145
|
+
entry: [
|
|
146
|
+
{
|
|
147
|
+
id: 'entry-1',
|
|
148
|
+
changes: [
|
|
149
|
+
{
|
|
150
|
+
value: {
|
|
151
|
+
messaging_product: 'whatsapp',
|
|
152
|
+
metadata: {
|
|
153
|
+
display_phone_number: '+1234567890',
|
|
154
|
+
phone_number_id: '123456789',
|
|
155
|
+
},
|
|
156
|
+
messages: [
|
|
157
|
+
{
|
|
158
|
+
from: '0987654321',
|
|
159
|
+
id: 'wamid.img123',
|
|
160
|
+
timestamp: '1700000000',
|
|
161
|
+
type: 'image',
|
|
162
|
+
image: {
|
|
163
|
+
id: 'media-1',
|
|
164
|
+
mime_type: 'image/jpeg',
|
|
165
|
+
sha256: 'abc123',
|
|
166
|
+
caption: 'Check this out',
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
field: 'messages',
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(receivedMessages).toHaveLength(1);
|
|
179
|
+
const msg = receivedMessages[0] as {
|
|
180
|
+
content: string;
|
|
181
|
+
attachments: Array<{ type: string; mimeType: string }>;
|
|
182
|
+
};
|
|
183
|
+
expect(msg.content).toBe('Check this out');
|
|
184
|
+
expect(msg.attachments).toHaveLength(1);
|
|
185
|
+
expect(msg.attachments[0].type).toBe('image');
|
|
186
|
+
expect(msg.attachments[0].mimeType).toBe('image/jpeg');
|
|
187
|
+
|
|
188
|
+
await adapter.disconnect();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should ignore non-whatsapp objects', async () => {
|
|
192
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
|
|
193
|
+
ok: true,
|
|
194
|
+
json: async () => ({ id: '123456789' }),
|
|
195
|
+
} as Response);
|
|
196
|
+
await adapter.connect();
|
|
197
|
+
|
|
198
|
+
const receivedMessages: unknown[] = [];
|
|
199
|
+
adapter.onMessage(async (msg) => {
|
|
200
|
+
receivedMessages.push(msg);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await adapter.handleWebhook({
|
|
204
|
+
object: 'instagram',
|
|
205
|
+
entry: [],
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
expect(receivedMessages).toHaveLength(0);
|
|
209
|
+
|
|
210
|
+
await adapter.disconnect();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should handle reply context', async () => {
|
|
214
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
|
|
215
|
+
ok: true,
|
|
216
|
+
json: async () => ({ id: '123456789' }),
|
|
217
|
+
} as Response);
|
|
218
|
+
await adapter.connect();
|
|
219
|
+
|
|
220
|
+
const receivedMessages: unknown[] = [];
|
|
221
|
+
adapter.onMessage(async (msg) => {
|
|
222
|
+
receivedMessages.push(msg);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
await adapter.handleWebhook({
|
|
226
|
+
object: 'whatsapp_business_account',
|
|
227
|
+
entry: [
|
|
228
|
+
{
|
|
229
|
+
id: 'entry-1',
|
|
230
|
+
changes: [
|
|
231
|
+
{
|
|
232
|
+
value: {
|
|
233
|
+
messaging_product: 'whatsapp',
|
|
234
|
+
metadata: {
|
|
235
|
+
display_phone_number: '+1234567890',
|
|
236
|
+
phone_number_id: '123456789',
|
|
237
|
+
},
|
|
238
|
+
messages: [
|
|
239
|
+
{
|
|
240
|
+
from: '0987654321',
|
|
241
|
+
id: 'wamid.reply123',
|
|
242
|
+
timestamp: '1700000000',
|
|
243
|
+
type: 'text',
|
|
244
|
+
text: { body: 'This is a reply' },
|
|
245
|
+
context: {
|
|
246
|
+
from: '1234567890',
|
|
247
|
+
id: 'wamid.original123',
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
field: 'messages',
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
expect(receivedMessages).toHaveLength(1);
|
|
260
|
+
const msg = receivedMessages[0] as { replyToId: string };
|
|
261
|
+
expect(msg.replyToId).toBe('wamid.original123');
|
|
262
|
+
|
|
263
|
+
await adapter.disconnect();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should send a message successfully', async () => {
|
|
267
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
|
|
268
|
+
ok: true,
|
|
269
|
+
json: async () => ({ id: '123456789' }),
|
|
270
|
+
} as Response);
|
|
271
|
+
await adapter.connect();
|
|
272
|
+
|
|
273
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
|
|
274
|
+
ok: true,
|
|
275
|
+
json: async () => ({
|
|
276
|
+
messaging_product: 'whatsapp',
|
|
277
|
+
contacts: [{ wa_id: '0987654321' }],
|
|
278
|
+
messages: [{ id: 'wamid.sent123' }],
|
|
279
|
+
}),
|
|
280
|
+
} as Response);
|
|
281
|
+
|
|
282
|
+
const result = await adapter.send('0987654321', {
|
|
283
|
+
content: 'Hello from bot!',
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
expect(result.success).toBe(true);
|
|
287
|
+
expect(result.messageId).toBe('wamid.sent123');
|
|
288
|
+
|
|
289
|
+
await adapter.disconnect();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should send a reply with context', async () => {
|
|
293
|
+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
|
|
294
|
+
ok: true,
|
|
295
|
+
json: async () => ({ id: '123456789' }),
|
|
296
|
+
} as Response);
|
|
297
|
+
await adapter.connect();
|
|
298
|
+
|
|
299
|
+
fetchSpy.mockResolvedValueOnce({
|
|
300
|
+
ok: true,
|
|
301
|
+
json: async () => ({
|
|
302
|
+
messaging_product: 'whatsapp',
|
|
303
|
+
contacts: [{ wa_id: '0987654321' }],
|
|
304
|
+
messages: [{ id: 'wamid.reply-sent' }],
|
|
305
|
+
}),
|
|
306
|
+
} as Response);
|
|
307
|
+
|
|
308
|
+
await adapter.send('0987654321', {
|
|
309
|
+
content: 'Reply',
|
|
310
|
+
replyToId: 'wamid.original',
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Verify the context was included in the body
|
|
314
|
+
const sendCall = fetchSpy.mock.calls.find(
|
|
315
|
+
(call) =>
|
|
316
|
+
typeof call[0] === 'string' &&
|
|
317
|
+
call[0].includes('/messages')
|
|
318
|
+
);
|
|
319
|
+
expect(sendCall).toBeDefined();
|
|
320
|
+
const body = JSON.parse(sendCall![1]?.body as string);
|
|
321
|
+
expect(body.context.message_id).toBe('wamid.original');
|
|
322
|
+
|
|
323
|
+
await adapter.disconnect();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('should handle send errors', async () => {
|
|
327
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
|
|
328
|
+
ok: true,
|
|
329
|
+
json: async () => ({ id: '123456789' }),
|
|
330
|
+
} as Response);
|
|
331
|
+
await adapter.connect();
|
|
332
|
+
|
|
333
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
|
|
334
|
+
ok: false,
|
|
335
|
+
status: 400,
|
|
336
|
+
statusText: 'Bad Request',
|
|
337
|
+
text: async () => 'Invalid phone number',
|
|
338
|
+
} as unknown as Response);
|
|
339
|
+
|
|
340
|
+
const result = await adapter.send('invalid', {
|
|
341
|
+
content: 'Should fail',
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
expect(result.success).toBe(false);
|
|
345
|
+
expect(result.error).toContain('400');
|
|
346
|
+
|
|
347
|
+
await adapter.disconnect();
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
describe('sender filtering', () => {
|
|
351
|
+
it('should allow all messages when allowedNumbers is not set', async () => {
|
|
352
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
|
|
353
|
+
ok: true,
|
|
354
|
+
json: async () => ({ id: '123456789' }),
|
|
355
|
+
} as Response);
|
|
356
|
+
await adapter.connect();
|
|
357
|
+
|
|
358
|
+
const receivedMessages: unknown[] = [];
|
|
359
|
+
adapter.onMessage(async (msg) => {
|
|
360
|
+
receivedMessages.push(msg);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
await adapter.handleWebhook({
|
|
364
|
+
object: 'whatsapp_business_account',
|
|
365
|
+
entry: [{
|
|
366
|
+
id: 'entry-1',
|
|
367
|
+
changes: [{
|
|
368
|
+
value: {
|
|
369
|
+
messaging_product: 'whatsapp',
|
|
370
|
+
metadata: { display_phone_number: '+1234567890', phone_number_id: '123456789' },
|
|
371
|
+
messages: [{
|
|
372
|
+
from: '0987654321',
|
|
373
|
+
id: 'wamid.filter1',
|
|
374
|
+
timestamp: '1700000000',
|
|
375
|
+
type: 'text',
|
|
376
|
+
text: { body: 'Hello!' },
|
|
377
|
+
}],
|
|
378
|
+
},
|
|
379
|
+
field: 'messages',
|
|
380
|
+
}],
|
|
381
|
+
}],
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
expect(receivedMessages).toHaveLength(1);
|
|
385
|
+
await adapter.disconnect();
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('should allow messages from allowed numbers', async () => {
|
|
389
|
+
const filteredAdapter = new WhatsAppAdapter({
|
|
390
|
+
phoneNumberId: '123456789',
|
|
391
|
+
accessToken: 'test-access-token',
|
|
392
|
+
verifyToken: 'test-verify-token',
|
|
393
|
+
allowedNumbers: ['0987654321'],
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
|
|
397
|
+
ok: true,
|
|
398
|
+
json: async () => ({ id: '123456789' }),
|
|
399
|
+
} as Response);
|
|
400
|
+
await filteredAdapter.connect();
|
|
401
|
+
|
|
402
|
+
const receivedMessages: unknown[] = [];
|
|
403
|
+
filteredAdapter.onMessage(async (msg) => {
|
|
404
|
+
receivedMessages.push(msg);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
await filteredAdapter.handleWebhook({
|
|
408
|
+
object: 'whatsapp_business_account',
|
|
409
|
+
entry: [{
|
|
410
|
+
id: 'entry-1',
|
|
411
|
+
changes: [{
|
|
412
|
+
value: {
|
|
413
|
+
messaging_product: 'whatsapp',
|
|
414
|
+
metadata: { display_phone_number: '+1234567890', phone_number_id: '123456789' },
|
|
415
|
+
messages: [{
|
|
416
|
+
from: '0987654321',
|
|
417
|
+
id: 'wamid.filter2',
|
|
418
|
+
timestamp: '1700000000',
|
|
419
|
+
type: 'text',
|
|
420
|
+
text: { body: 'Allowed!' },
|
|
421
|
+
}],
|
|
422
|
+
},
|
|
423
|
+
field: 'messages',
|
|
424
|
+
}],
|
|
425
|
+
}],
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
expect(receivedMessages).toHaveLength(1);
|
|
429
|
+
await filteredAdapter.disconnect();
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('should block messages from non-allowed numbers', async () => {
|
|
433
|
+
const { audit } = await import('@auxiora/audit');
|
|
434
|
+
const filteredAdapter = new WhatsAppAdapter({
|
|
435
|
+
phoneNumberId: '123456789',
|
|
436
|
+
accessToken: 'test-access-token',
|
|
437
|
+
verifyToken: 'test-verify-token',
|
|
438
|
+
allowedNumbers: ['0987654321'],
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
|
|
442
|
+
ok: true,
|
|
443
|
+
json: async () => ({ id: '123456789' }),
|
|
444
|
+
} as Response);
|
|
445
|
+
await filteredAdapter.connect();
|
|
446
|
+
|
|
447
|
+
const receivedMessages: unknown[] = [];
|
|
448
|
+
filteredAdapter.onMessage(async (msg) => {
|
|
449
|
+
receivedMessages.push(msg);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
await filteredAdapter.handleWebhook({
|
|
453
|
+
object: 'whatsapp_business_account',
|
|
454
|
+
entry: [{
|
|
455
|
+
id: 'entry-1',
|
|
456
|
+
changes: [{
|
|
457
|
+
value: {
|
|
458
|
+
messaging_product: 'whatsapp',
|
|
459
|
+
metadata: { display_phone_number: '+1234567890', phone_number_id: '123456789' },
|
|
460
|
+
messages: [{
|
|
461
|
+
from: '5555555555',
|
|
462
|
+
id: 'wamid.filter3',
|
|
463
|
+
timestamp: '1700000000',
|
|
464
|
+
type: 'text',
|
|
465
|
+
text: { body: 'Blocked!' },
|
|
466
|
+
}],
|
|
467
|
+
},
|
|
468
|
+
field: 'messages',
|
|
469
|
+
}],
|
|
470
|
+
}],
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
expect(receivedMessages).toHaveLength(0);
|
|
474
|
+
expect(audit).toHaveBeenCalledWith('message.filtered', expect.objectContaining({
|
|
475
|
+
channelType: 'whatsapp',
|
|
476
|
+
senderId: '5555555555',
|
|
477
|
+
reason: 'number_not_allowed',
|
|
478
|
+
}));
|
|
479
|
+
await filteredAdapter.disconnect();
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('should handle message handler errors gracefully', async () => {
|
|
484
|
+
vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce({
|
|
485
|
+
ok: true,
|
|
486
|
+
json: async () => ({ id: '123456789' }),
|
|
487
|
+
} as Response);
|
|
488
|
+
await adapter.connect();
|
|
489
|
+
|
|
490
|
+
const errorHandler = vi.fn();
|
|
491
|
+
adapter.onError(errorHandler);
|
|
492
|
+
adapter.onMessage(async () => {
|
|
493
|
+
throw new Error('Handler error');
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
await adapter.handleWebhook({
|
|
497
|
+
object: 'whatsapp_business_account',
|
|
498
|
+
entry: [
|
|
499
|
+
{
|
|
500
|
+
id: 'entry-1',
|
|
501
|
+
changes: [
|
|
502
|
+
{
|
|
503
|
+
value: {
|
|
504
|
+
messaging_product: 'whatsapp',
|
|
505
|
+
metadata: {
|
|
506
|
+
display_phone_number: '+1234567890',
|
|
507
|
+
phone_number_id: '123456789',
|
|
508
|
+
},
|
|
509
|
+
messages: [
|
|
510
|
+
{
|
|
511
|
+
from: '0987654321',
|
|
512
|
+
id: 'wamid.err123',
|
|
513
|
+
timestamp: '1700000000',
|
|
514
|
+
type: 'text',
|
|
515
|
+
text: { body: 'Trigger error' },
|
|
516
|
+
},
|
|
517
|
+
],
|
|
518
|
+
},
|
|
519
|
+
field: 'messages',
|
|
520
|
+
},
|
|
521
|
+
],
|
|
522
|
+
},
|
|
523
|
+
],
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
expect(errorHandler).toHaveBeenCalledWith(expect.any(Error));
|
|
527
|
+
|
|
528
|
+
await adapter.disconnect();
|
|
529
|
+
});
|
|
530
|
+
});
|