@ihazz/bitrix24 0.2.5 → 1.0.1

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.
@@ -1,12 +1,7 @@
1
+ import { stringify as stringifyQueryString } from 'qs';
1
2
  import { describe, it, expect, vi, afterEach } from 'vitest';
2
- import { InboundHandler, normalizeMessageEvent } from '../src/inbound-handler.js';
3
- import type { B24MessageEvent, B24MsgContext, B24BotEntry } from '../src/types.js';
4
-
5
- // Import fixtures
6
- import textFixture from './fixtures/onimbotmessageadd-text.json';
7
- import fileFixture from './fixtures/onimbotmessageadd-file.json';
8
- import joinFixture from './fixtures/onimbotjoinchat.json';
9
- import commandFixture from './fixtures/onimcommandadd.json';
3
+ import { InboundHandler } from '../src/inbound-handler.js';
4
+ import type { B24MsgContext, FetchContext } from '../src/types.js';
10
5
 
11
6
  const silentLogger = {
12
7
  info: () => {},
@@ -15,6 +10,91 @@ const silentLogger = {
15
10
  debug: () => {},
16
11
  };
17
12
 
13
+ const fetchCtx: FetchContext = {
14
+ webhookUrl: 'https://test.bitrix24.com/rest/1/token/',
15
+ botId: 2081,
16
+ botToken: 'bot_token_test_abc123',
17
+ };
18
+
19
+ const baseBot = {
20
+ id: 2081,
21
+ code: 'openclaw',
22
+ type: 'bot',
23
+ isHidden: false,
24
+ isSupportOpenline: false,
25
+ isReactionsEnabled: true,
26
+ backgroundId: null,
27
+ language: 'ru',
28
+ eventMode: 'fetch',
29
+ };
30
+
31
+ const baseUser = {
32
+ id: 1,
33
+ active: true,
34
+ name: 'Test User',
35
+ firstName: 'Test',
36
+ lastName: 'User',
37
+ workPosition: 'Developer',
38
+ color: '#df532d',
39
+ avatar: '',
40
+ gender: 'M',
41
+ birthday: '',
42
+ extranet: false,
43
+ bot: false,
44
+ connector: false,
45
+ externalAuthId: 'default',
46
+ status: 'online',
47
+ idle: false,
48
+ lastActivityDate: false,
49
+ absent: false,
50
+ departments: [1],
51
+ phones: false,
52
+ type: 'employee',
53
+ };
54
+
55
+ function createFetchMessageEvent(overrides: Record<string, unknown> = {}) {
56
+ return {
57
+ eventId: 1001,
58
+ type: 'ONIMBOTV2MESSAGEADD',
59
+ date: '2026-03-18T08:00:00+02:00',
60
+ data: {
61
+ bot: baseBot,
62
+ message: {
63
+ id: 490659,
64
+ chatId: 40985,
65
+ authorId: 1,
66
+ date: '2026-03-18T08:00:00+02:00',
67
+ text: 'Hello, how are you?',
68
+ isSystem: false,
69
+ uuid: '',
70
+ forward: null,
71
+ params: {},
72
+ viewedByOthers: false,
73
+ },
74
+ chat: {
75
+ id: 40985,
76
+ dialogId: '1',
77
+ type: 'private',
78
+ name: 'Test User',
79
+ entityType: '',
80
+ owner: 1,
81
+ avatar: '',
82
+ color: '#ab7761',
83
+ },
84
+ user: baseUser,
85
+ language: 'ru',
86
+ ...overrides,
87
+ },
88
+ };
89
+ }
90
+
91
+ function createWebhookEvent(eventType: string, data: Record<string, unknown>) {
92
+ return {
93
+ event: eventType,
94
+ data,
95
+ };
96
+ }
97
+
18
98
  describe('InboundHandler', () => {
19
99
  let handler: InboundHandler;
20
100
 
@@ -22,15 +102,15 @@ describe('InboundHandler', () => {
22
102
  handler?.destroy();
23
103
  });
24
104
 
25
- it('dispatches text message events', async () => {
105
+ it('dispatches fetch message events', async () => {
26
106
  const onMessage = vi.fn();
27
107
  handler = new InboundHandler({
28
- config: { dmPolicy: 'open' },
108
+ config: { dmPolicy: 'pairing' },
29
109
  logger: silentLogger,
30
110
  onMessage,
31
111
  });
32
112
 
33
- const result = await handler.handleWebhook(textFixture);
113
+ const result = await handler.handleFetchEvent(createFetchMessageEvent(), fetchCtx);
34
114
  expect(result).toBe(true);
35
115
  expect(onMessage).toHaveBeenCalledOnce();
36
116
 
@@ -39,125 +119,530 @@ describe('InboundHandler', () => {
39
119
  expect(ctx.text).toBe('Hello, how are you?');
40
120
  expect(ctx.senderId).toBe('1');
41
121
  expect(ctx.senderName).toBe('Test User');
122
+ expect(ctx.senderFirstName).toBe('Test');
42
123
  expect(ctx.chatId).toBe('1');
124
+ expect(ctx.chatInternalId).toBe('40985');
125
+ expect(ctx.messageId).toBe('490659');
126
+ expect(ctx.replyToMessageId).toBeUndefined();
127
+ expect(ctx.isForwarded).toBe(false);
43
128
  expect(ctx.isDm).toBe(true);
44
129
  expect(ctx.isGroup).toBe(false);
45
- expect(ctx.botToken).toBe('bot_token_test_abc123');
46
- expect(ctx.userToken).toBe('user_token_test_def456');
130
+ expect(ctx.media).toEqual([]);
131
+ expect(ctx.language).toBe('ru');
132
+ expect(ctx.botId).toBe(2081);
133
+ });
134
+
135
+ it('keeps incoming dialogId for direct chats without replacing it by user id', async () => {
136
+ const onMessage = vi.fn();
137
+ handler = new InboundHandler({
138
+ config: { dmPolicy: 'pairing' },
139
+ logger: silentLogger,
140
+ onMessage,
141
+ });
142
+
143
+ const event = createFetchMessageEvent({
144
+ chat: {
145
+ id: 40985,
146
+ dialogId: '123',
147
+ type: 'private',
148
+ name: 'Test User',
149
+ entityType: '',
150
+ owner: 55,
151
+ avatar: '',
152
+ color: '#ab7761',
153
+ },
154
+ user: {
155
+ ...baseUser,
156
+ id: 55,
157
+ },
158
+ });
159
+
160
+ await handler.handleFetchEvent(event, fetchCtx);
161
+
162
+ const ctx = onMessage.mock.calls[0][0] as B24MsgContext;
163
+ expect(ctx.chatId).toBe('123');
164
+ expect(ctx.senderId).toBe('55');
165
+ });
166
+
167
+ it('deduplicates fetch messages by message id', async () => {
168
+ const onMessage = vi.fn();
169
+ handler = new InboundHandler({
170
+ config: { dmPolicy: 'pairing' },
171
+ logger: silentLogger,
172
+ onMessage,
173
+ });
174
+
175
+ const event = createFetchMessageEvent();
176
+ await handler.handleFetchEvent(event, fetchCtx);
177
+ await handler.handleFetchEvent(event, fetchCtx);
178
+
179
+ expect(onMessage).toHaveBeenCalledOnce();
180
+ });
181
+
182
+ it('extracts media ids from V2 message params', async () => {
183
+ const onMessage = vi.fn();
184
+ handler = new InboundHandler({
185
+ config: { dmPolicy: 'pairing' },
186
+ logger: silentLogger,
187
+ onMessage,
188
+ });
189
+
190
+ const event = createFetchMessageEvent({
191
+ message: {
192
+ id: 492035,
193
+ chatId: 40985,
194
+ authorId: 1,
195
+ date: '2026-03-18T08:00:00+02:00',
196
+ text: '',
197
+ isSystem: false,
198
+ uuid: '',
199
+ forward: null,
200
+ params: { FILE_ID: [94611, '94612'] },
201
+ viewedByOthers: false,
202
+ },
203
+ });
204
+
205
+ await handler.handleFetchEvent(event, fetchCtx);
206
+
207
+ const ctx = onMessage.mock.calls[0][0] as B24MsgContext;
208
+ expect(ctx.media).toEqual([
209
+ {
210
+ id: '94611',
211
+ name: 'file_94611',
212
+ extension: '',
213
+ size: 0,
214
+ type: 'file',
215
+ },
216
+ {
217
+ id: '94612',
218
+ name: 'file_94612',
219
+ extension: '',
220
+ size: 0,
221
+ type: 'file',
222
+ },
223
+ ]);
224
+ });
225
+
226
+ it('extracts reply target id from V2 message params', async () => {
227
+ const onMessage = vi.fn();
228
+ handler = new InboundHandler({
229
+ config: { dmPolicy: 'pairing' },
230
+ logger: silentLogger,
231
+ onMessage,
232
+ });
233
+
234
+ const event = createFetchMessageEvent({
235
+ message: {
236
+ id: 492036,
237
+ chatId: 40985,
238
+ authorId: 1,
239
+ date: '2026-03-18T08:00:00+02:00',
240
+ text: 'reply text',
241
+ isSystem: false,
242
+ uuid: '',
243
+ forward: null,
244
+ params: { REPLY_ID: '490600' },
245
+ viewedByOthers: false,
246
+ },
247
+ });
248
+
249
+ await handler.handleFetchEvent(event, fetchCtx);
250
+
251
+ const ctx = onMessage.mock.calls[0][0] as B24MsgContext;
252
+ expect(ctx.replyToMessageId).toBe('490600');
253
+ });
254
+
255
+ it('marks forwarded V2 messages', async () => {
256
+ const onMessage = vi.fn();
257
+ handler = new InboundHandler({
258
+ config: { dmPolicy: 'pairing' },
259
+ logger: silentLogger,
260
+ onMessage,
261
+ });
262
+
263
+ const event = createFetchMessageEvent({
264
+ message: {
265
+ id: 492037,
266
+ chatId: 40985,
267
+ authorId: 1,
268
+ date: '2026-03-18T08:00:00+02:00',
269
+ text: 'forwarded body',
270
+ isSystem: false,
271
+ uuid: '',
272
+ forward: { id: 'chat208/6717', userId: 1, chatType: 'chat' },
273
+ params: {},
274
+ viewedByOthers: false,
275
+ },
276
+ });
277
+
278
+ await handler.handleFetchEvent(event, fetchCtx);
279
+
280
+ const ctx = onMessage.mock.calls[0][0] as B24MsgContext;
281
+ expect(ctx.isForwarded).toBe(true);
47
282
  });
48
283
 
49
- it('deduplicates messages', async () => {
284
+ it('dispatches webhook message events from JSON body', async () => {
50
285
  const onMessage = vi.fn();
51
286
  handler = new InboundHandler({
52
- config: { dmPolicy: 'open' },
287
+ config: { webhookUrl: fetchCtx.webhookUrl, botToken: fetchCtx.botToken, dmPolicy: 'pairing' },
53
288
  logger: silentLogger,
54
289
  onMessage,
55
290
  });
56
291
 
57
- await handler.handleWebhook(textFixture);
58
- await handler.handleWebhook(textFixture);
292
+ const payload = createWebhookEvent('ONIMBOTV2MESSAGEADD', {
293
+ bot: {
294
+ id: '2081',
295
+ code: 'openclaw',
296
+ auth: {
297
+ access_token: 'bot_token_test_abc123',
298
+ refresh_token: 'refresh_token_test',
299
+ },
300
+ },
301
+ message: {
302
+ id: '490659',
303
+ chatId: '40985',
304
+ authorId: '1',
305
+ date: '2026-03-18T08:00:00+02:00',
306
+ text: 'Hello from webhook',
307
+ isSystem: '0',
308
+ uuid: '',
309
+ forward: '',
310
+ params: {},
311
+ viewedByOthers: '0',
312
+ },
313
+ chat: {
314
+ id: '40985',
315
+ dialogId: '1',
316
+ type: 'private',
317
+ name: 'Test User',
318
+ entityType: '',
319
+ owner: '1',
320
+ avatar: '',
321
+ color: '#ab7761',
322
+ },
323
+ user: {
324
+ ...baseUser,
325
+ id: '1',
326
+ active: '1',
327
+ extranet: '0',
328
+ bot: '0',
329
+ connector: '0',
330
+ idle: '0',
331
+ lastActivityDate: '0',
332
+ absent: '0',
333
+ departments: ['1'],
334
+ phones: '0',
335
+ },
336
+ language: 'ru',
337
+ });
59
338
 
339
+ const result = await handler.handleWebhook(JSON.stringify(payload));
340
+ expect(result).toBe(true);
60
341
  expect(onMessage).toHaveBeenCalledOnce();
342
+
343
+ const ctx = onMessage.mock.calls[0][0] as B24MsgContext;
344
+ expect(ctx.text).toBe('Hello from webhook');
345
+ expect(ctx.chatId).toBe('1');
346
+ expect(ctx.messageId).toBe('490659');
61
347
  });
62
348
 
63
- it('dispatches all messages (access control handled externally)', async () => {
349
+ it('dispatches webhook message events from query string body', async () => {
64
350
  const onMessage = vi.fn();
65
351
  handler = new InboundHandler({
66
- config: { dmPolicy: 'allowlist', allowFrom: ['99'] },
352
+ config: { webhookUrl: fetchCtx.webhookUrl, botToken: fetchCtx.botToken, dmPolicy: 'pairing' },
67
353
  logger: silentLogger,
68
354
  onMessage,
69
355
  });
70
356
 
71
- await handler.handleWebhook(textFixture);
72
- // Access control is now handled in channel.ts onMessage callback, not in InboundHandler
357
+ const payload = createWebhookEvent('ONIMBOTV2MESSAGEADD', {
358
+ bot: {
359
+ id: '2081',
360
+ code: 'openclaw',
361
+ auth: {
362
+ access_token: 'bot_token_test_abc123',
363
+ refresh_token: 'refresh_token_test',
364
+ },
365
+ },
366
+ message: {
367
+ id: '490660',
368
+ chatId: '40985',
369
+ authorId: '1',
370
+ date: '2026-03-18T08:00:00+02:00',
371
+ text: 'Hello from qs',
372
+ isSystem: '0',
373
+ uuid: '',
374
+ forward: '',
375
+ params: {},
376
+ viewedByOthers: '0',
377
+ },
378
+ chat: {
379
+ id: '40985',
380
+ dialogId: '1',
381
+ type: 'private',
382
+ name: 'Test User',
383
+ entityType: '',
384
+ owner: '1',
385
+ avatar: '',
386
+ color: '#ab7761',
387
+ },
388
+ user: {
389
+ ...baseUser,
390
+ id: '1',
391
+ active: '1',
392
+ extranet: '0',
393
+ bot: '0',
394
+ connector: '0',
395
+ idle: '0',
396
+ lastActivityDate: '0',
397
+ absent: '0',
398
+ departments: ['1'],
399
+ phones: '0',
400
+ },
401
+ language: 'ru',
402
+ });
403
+
404
+ const result = await handler.handleWebhook(stringifyQueryString(payload));
405
+ expect(result).toBe(true);
73
406
  expect(onMessage).toHaveBeenCalledOnce();
74
407
  });
75
408
 
76
409
  it('dispatches join chat events', async () => {
77
410
  const onJoinChat = vi.fn();
78
411
  handler = new InboundHandler({
79
- config: { dmPolicy: 'open' },
412
+ config: { dmPolicy: 'pairing' },
80
413
  logger: silentLogger,
81
414
  onJoinChat,
82
415
  });
83
416
 
84
- const result = await handler.handleWebhook(joinFixture);
417
+ const event = {
418
+ eventId: 1002,
419
+ type: 'ONIMBOTV2JOINCHAT',
420
+ date: '2026-03-18T08:00:00+02:00',
421
+ data: {
422
+ bot: baseBot,
423
+ dialogId: '1',
424
+ chat: {
425
+ id: 40985,
426
+ dialogId: '1',
427
+ type: 'private',
428
+ name: 'Test User',
429
+ entityType: '',
430
+ owner: 1,
431
+ avatar: '',
432
+ color: '#ab7761',
433
+ },
434
+ user: baseUser,
435
+ language: 'ru',
436
+ },
437
+ };
438
+
439
+ const result = await handler.handleFetchEvent(event, fetchCtx);
85
440
  expect(result).toBe(true);
86
441
  expect(onJoinChat).toHaveBeenCalledOnce();
442
+ expect(onJoinChat).toHaveBeenCalledWith({
443
+ dialogId: '1',
444
+ chatType: 'private',
445
+ language: 'ru',
446
+ fetchCtx,
447
+ });
87
448
  });
88
449
 
89
450
  it('dispatches command events', async () => {
90
451
  const onCommand = vi.fn();
91
452
  handler = new InboundHandler({
92
- config: { dmPolicy: 'open' },
453
+ config: { dmPolicy: 'pairing' },
93
454
  logger: silentLogger,
94
455
  onCommand,
95
456
  });
96
457
 
97
- const result = await handler.handleWebhook(commandFixture);
458
+ const event = {
459
+ eventId: 1003,
460
+ type: 'ONIMBOTV2COMMANDADD',
461
+ date: '2026-03-18T08:00:00+02:00',
462
+ data: {
463
+ bot: baseBot,
464
+ command: {
465
+ id: 53,
466
+ command: '/help',
467
+ params: 'topic',
468
+ context: 'textarea',
469
+ },
470
+ message: {
471
+ id: 490667,
472
+ chatId: 40985,
473
+ authorId: 1,
474
+ date: '2026-03-18T08:00:00+02:00',
475
+ text: '/help topic',
476
+ isSystem: false,
477
+ uuid: '',
478
+ forward: null,
479
+ params: {},
480
+ viewedByOthers: false,
481
+ },
482
+ chat: {
483
+ id: 40985,
484
+ dialogId: '1',
485
+ type: 'private',
486
+ name: 'Test User',
487
+ entityType: '',
488
+ owner: 1,
489
+ avatar: '',
490
+ color: '#ab7761',
491
+ },
492
+ user: baseUser,
493
+ language: 'ru',
494
+ },
495
+ };
496
+
497
+ const result = await handler.handleFetchEvent(event, fetchCtx);
98
498
  expect(result).toBe(true);
99
499
  expect(onCommand).toHaveBeenCalledOnce();
500
+ expect(onCommand).toHaveBeenCalledWith({
501
+ commandId: 53,
502
+ commandName: 'help',
503
+ commandParams: 'topic',
504
+ commandText: '/help topic',
505
+ senderId: '1',
506
+ dialogId: '1',
507
+ chatType: 'P',
508
+ messageId: '490667',
509
+ language: 'ru',
510
+ fetchCtx,
511
+ });
100
512
  });
101
513
 
102
- it('returns false for unknown event types', async () => {
514
+ it('dispatches keyboard command events even when message payload is incomplete', async () => {
515
+ const onCommand = vi.fn();
103
516
  handler = new InboundHandler({
104
- config: { dmPolicy: 'open' },
517
+ config: { dmPolicy: 'pairing' },
105
518
  logger: silentLogger,
519
+ onCommand,
106
520
  });
107
521
 
108
- const result = await handler.handleWebhook({ event: 'UNKNOWN_EVENT' });
109
- expect(result).toBe(false);
522
+ const event = {
523
+ eventId: 1004,
524
+ type: 'ONIMBOTV2COMMANDADD',
525
+ date: '2026-03-18T08:00:00+02:00',
526
+ data: {
527
+ bot: baseBot,
528
+ command: {
529
+ id: 54,
530
+ command: '/status',
531
+ context: 'keyboard',
532
+ },
533
+ chat: {
534
+ id: 40985,
535
+ dialogId: 1,
536
+ type: 'private',
537
+ name: 'Test User',
538
+ entityType: '',
539
+ owner: 1,
540
+ avatar: '',
541
+ color: '#ab7761',
542
+ },
543
+ user: baseUser,
544
+ language: 'ru',
545
+ },
546
+ };
547
+
548
+ const result = await handler.handleFetchEvent(event as never, fetchCtx);
549
+ expect(result).toBe(true);
550
+ expect(onCommand).toHaveBeenCalledOnce();
551
+ expect(onCommand).toHaveBeenCalledWith({
552
+ commandId: 54,
553
+ commandName: 'status',
554
+ commandParams: '',
555
+ commandText: '/status',
556
+ senderId: '1',
557
+ dialogId: '1',
558
+ chatType: 'P',
559
+ messageId: '1004',
560
+ language: 'ru',
561
+ fetchCtx,
562
+ });
110
563
  });
111
564
 
112
- it('returns false for missing event type', async () => {
565
+ it('builds command text from command payload instead of message text', async () => {
566
+ const onCommand = vi.fn();
113
567
  handler = new InboundHandler({
114
- config: { dmPolicy: 'open' },
568
+ config: { dmPolicy: 'pairing' },
115
569
  logger: silentLogger,
570
+ onCommand,
116
571
  });
117
572
 
118
- const result = await handler.handleWebhook({});
119
- expect(result).toBe(false);
573
+ const event = {
574
+ eventId: 1005,
575
+ type: 'ONIMBOTV2COMMANDADD',
576
+ date: '2026-03-18T08:00:00+02:00',
577
+ data: {
578
+ bot: baseBot,
579
+ command: {
580
+ id: 55,
581
+ command: '/status',
582
+ params: '',
583
+ context: 'keyboard',
584
+ },
585
+ message: {
586
+ id: 490668,
587
+ chatId: 40985,
588
+ authorId: 1,
589
+ date: '2026-03-18T08:00:00+02:00',
590
+ text: '/commands',
591
+ isSystem: false,
592
+ uuid: '',
593
+ forward: null,
594
+ params: {},
595
+ viewedByOthers: false,
596
+ },
597
+ chat: {
598
+ id: 40985,
599
+ dialogId: '1',
600
+ type: 'private',
601
+ name: 'Test User',
602
+ entityType: '',
603
+ owner: 1,
604
+ avatar: '',
605
+ color: '#ab7761',
606
+ },
607
+ user: baseUser,
608
+ language: 'ru',
609
+ },
610
+ };
611
+
612
+ const result = await handler.handleFetchEvent(event as never, fetchCtx);
613
+ expect(result).toBe(true);
614
+ expect(onCommand).toHaveBeenCalledOnce();
615
+ expect(onCommand).toHaveBeenCalledWith({
616
+ commandId: 55,
617
+ commandName: 'status',
618
+ commandParams: '',
619
+ commandText: '/status',
620
+ senderId: '1',
621
+ dialogId: '1',
622
+ chatType: 'P',
623
+ messageId: '490668',
624
+ language: 'ru',
625
+ fetchCtx,
626
+ });
120
627
  });
121
- });
122
628
 
123
- describe('normalizeMessageEvent', () => {
124
- it('normalizes a text message event', () => {
125
- const event = textFixture as unknown as B24MessageEvent;
126
- const botEntry = Object.values(event.data.BOT)[0] as B24BotEntry;
127
- const ctx = normalizeMessageEvent(event, botEntry);
629
+ it('returns true for unknown event types to acknowledge delivery', async () => {
630
+ handler = new InboundHandler({
631
+ config: { dmPolicy: 'pairing' },
632
+ logger: silentLogger,
633
+ });
128
634
 
129
- expect(ctx.channel).toBe('bitrix24');
130
- expect(ctx.senderId).toBe('1');
131
- expect(ctx.senderName).toBe('Test User');
132
- expect(ctx.senderFirstName).toBe('Test');
133
- expect(ctx.chatId).toBe('1');
134
- expect(ctx.chatInternalId).toBe('40985');
135
- expect(ctx.messageId).toBe('490659');
136
- expect(ctx.text).toBe('Hello, how are you?');
137
- expect(ctx.isDm).toBe(true);
138
- expect(ctx.isGroup).toBe(false);
139
- expect(ctx.media).toEqual([]);
140
- expect(ctx.platform).toBe('web');
141
- expect(ctx.language).toBe('ru');
142
- expect(ctx.botId).toBe(2081);
143
- expect(ctx.memberId).toBe('test_member_id_123');
144
- expect(ctx.clientEndpoint).toBe('https://test.bitrix24.com/rest/');
635
+ const result = await handler.handleWebhook({ event: 'UNKNOWN_EVENT', data: {} });
636
+ expect(result).toBe(true);
145
637
  });
146
638
 
147
- it('normalizes a file message event', () => {
148
- const event = fileFixture as unknown as B24MessageEvent;
149
- const botEntry = Object.values(event.data.BOT)[0] as B24BotEntry;
150
- const ctx = normalizeMessageEvent(event, botEntry);
151
-
152
- expect(ctx.text).toBe('');
153
- expect(ctx.media).toHaveLength(1);
154
- expect(ctx.media[0]).toEqual({
155
- id: '94611',
156
- name: 'document.txt',
157
- extension: 'txt',
158
- size: 101,
159
- type: 'file',
160
- urlDownload: 'https://test.bitrix24.com/disk/downloadFile/94611/',
639
+ it('returns false for missing event type', async () => {
640
+ handler = new InboundHandler({
641
+ config: { dmPolicy: 'pairing' },
642
+ logger: silentLogger,
161
643
  });
644
+
645
+ const result = await handler.handleWebhook({});
646
+ expect(result).toBe(false);
162
647
  });
163
648
  });