@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.
- package/README.md +138 -156
- package/index.ts +46 -11
- package/openclaw.plugin.json +1 -0
- package/package.json +1 -1
- package/skills/bitrix24/SKILL.md +96 -0
- package/src/access-control.ts +102 -48
- package/src/api.ts +434 -232
- package/src/channel.ts +1492 -366
- package/src/commands.ts +169 -31
- package/src/config-schema.ts +8 -3
- package/src/config.ts +11 -0
- package/src/dedup.ts +4 -0
- package/src/i18n.ts +127 -0
- package/src/inbound-handler.ts +306 -110
- package/src/media-service.ts +218 -65
- package/src/message-utils.ts +252 -10
- package/src/polling-service.ts +240 -0
- package/src/rate-limiter.ts +11 -6
- package/src/send-service.ts +140 -60
- package/src/types.ts +279 -185
- package/src/utils.ts +54 -3
- package/tests/access-control.test.ts +174 -58
- package/tests/api.test.ts +95 -0
- package/tests/channel.test.ts +231 -10
- package/tests/commands.test.ts +57 -0
- package/tests/config.test.ts +5 -1
- package/tests/i18n.test.ts +47 -0
- package/tests/inbound-handler.test.ts +554 -69
- package/tests/index.test.ts +94 -0
- package/tests/media-service.test.ts +146 -51
- package/tests/message-utils.test.ts +64 -0
- package/tests/polling-service.test.ts +77 -0
- package/tests/rate-limiter.test.ts +2 -2
- package/tests/send-service.test.ts +145 -0
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
checkAccess,
|
|
4
|
+
checkAccessWithPairing,
|
|
5
|
+
getWebhookUserId,
|
|
6
|
+
normalizeAllowEntry,
|
|
7
|
+
normalizeAllowList,
|
|
8
|
+
} from '../src/access-control.js';
|
|
3
9
|
import type { PluginRuntime, ChannelPairingAdapter } from '../src/runtime.js';
|
|
4
10
|
|
|
5
11
|
describe('normalizeAllowEntry', () => {
|
|
@@ -30,51 +36,72 @@ describe('normalizeAllowEntry', () => {
|
|
|
30
36
|
});
|
|
31
37
|
});
|
|
32
38
|
|
|
33
|
-
describe('
|
|
34
|
-
it('
|
|
35
|
-
expect(
|
|
36
|
-
expect(checkAccess('999', { dmPolicy: 'open' })).toBe(true);
|
|
39
|
+
describe('normalizeAllowList', () => {
|
|
40
|
+
it('normalizes, deduplicates and filters empty allowFrom entries', () => {
|
|
41
|
+
expect(normalizeAllowList([' bitrix24:42 ', '42', 'b24:7', ''])).toEqual(['42', '7']);
|
|
37
42
|
});
|
|
38
43
|
|
|
39
|
-
it('
|
|
40
|
-
expect(
|
|
44
|
+
it('returns empty list for missing allowFrom config', () => {
|
|
45
|
+
expect(normalizeAllowList(undefined)).toEqual([]);
|
|
41
46
|
});
|
|
47
|
+
});
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
expect(
|
|
46
|
-
expect(checkAccess('42', config)).toBe(true);
|
|
49
|
+
describe('getWebhookUserId', () => {
|
|
50
|
+
it('extracts owner ID from webhook URL', () => {
|
|
51
|
+
expect(getWebhookUserId('https://test.bitrix24.com/rest/42/token/')).toBe('42');
|
|
47
52
|
});
|
|
48
53
|
|
|
49
|
-
it('
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
it('normalizes prefixed user IDs', () => {
|
|
55
|
+
expect(getWebhookUserId('https://test.bitrix24.com/rest/b24:42/token/')).toBe('42');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('returns null for invalid URLs', () => {
|
|
59
|
+
expect(getWebhookUserId('not-a-url')).toBeNull();
|
|
52
60
|
});
|
|
53
61
|
|
|
54
|
-
it('
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
it('returns null when rest segment is missing', () => {
|
|
63
|
+
expect(getWebhookUserId('https://test.bitrix24.com/api/42/token/')).toBeNull();
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('checkAccess', () => {
|
|
68
|
+
it('defaults to webhookUser and denies without a valid webhook owner', () => {
|
|
69
|
+
expect(checkAccess('1', {})).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('allows only the webhook owner in webhookUser mode', () => {
|
|
73
|
+
const config = {
|
|
74
|
+
dmPolicy: 'webhookUser' as const,
|
|
75
|
+
webhookUrl: 'https://test.bitrix24.com/rest/42/token/',
|
|
76
|
+
};
|
|
77
|
+
|
|
57
78
|
expect(checkAccess('42', config)).toBe(true);
|
|
58
79
|
expect(checkAccess('99', config)).toBe(false);
|
|
59
80
|
});
|
|
60
81
|
|
|
61
|
-
it('
|
|
62
|
-
|
|
63
|
-
expect(checkAccess('1', config)).toBe(false);
|
|
82
|
+
it('allows webhook owner when dmPolicy is omitted', () => {
|
|
83
|
+
expect(checkAccess('42', { webhookUrl: 'https://test.bitrix24.com/rest/42/token/' })).toBe(true);
|
|
64
84
|
});
|
|
65
85
|
|
|
66
|
-
it('
|
|
67
|
-
const config = {
|
|
68
|
-
|
|
86
|
+
it('keeps senderId as the access identity even when direct dialogId differs', () => {
|
|
87
|
+
const config = {
|
|
88
|
+
dmPolicy: 'webhookUser' as const,
|
|
89
|
+
webhookUrl: 'https://test.bitrix24.com/rest/42/token/',
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
expect(checkAccess('42', config, { dialogId: '2386', isDirect: true })).toBe(true);
|
|
93
|
+
expect(checkAccess('77', config, { dialogId: '42', isDirect: true })).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('denies when webhookUser mode has no valid webhook owner', () => {
|
|
97
|
+
expect(checkAccess('42', { dmPolicy: 'webhookUser', webhookUrl: 'invalid' })).toBe(false);
|
|
69
98
|
});
|
|
70
99
|
|
|
71
|
-
it('returns false for pairing mode
|
|
100
|
+
it('returns false for pairing mode without runtime state', () => {
|
|
72
101
|
expect(checkAccess('1', { dmPolicy: 'pairing' })).toBe(false);
|
|
73
102
|
});
|
|
74
103
|
});
|
|
75
104
|
|
|
76
|
-
// ─── checkAccessWithPairing ─────────────────────────────────────────────────
|
|
77
|
-
|
|
78
105
|
function makeMockRuntime(storeAllowFrom: string[] = []): PluginRuntime {
|
|
79
106
|
return {
|
|
80
107
|
config: { loadConfig: () => ({}) },
|
|
@@ -88,7 +115,7 @@ function makeMockRuntime(storeAllowFrom: string[] = []): PluginRuntime {
|
|
|
88
115
|
pairing: {
|
|
89
116
|
readAllowFromStore: vi.fn().mockResolvedValue(storeAllowFrom),
|
|
90
117
|
upsertPairingRequest: vi.fn().mockResolvedValue({ code: 'ABCD1234', created: true }),
|
|
91
|
-
buildPairingReply: vi.fn().mockReturnValue(
|
|
118
|
+
buildPairingReply: vi.fn().mockReturnValue('Your pairing code: ABCD1234'),
|
|
92
119
|
},
|
|
93
120
|
},
|
|
94
121
|
logging: { getChildLogger: () => ({ info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() }) },
|
|
@@ -100,41 +127,112 @@ const mockAdapter: ChannelPairingAdapter = {
|
|
|
100
127
|
normalizeAllowEntry: (entry) => entry.replace(/^(bitrix24|b24|bx24):/i, ''),
|
|
101
128
|
};
|
|
102
129
|
|
|
103
|
-
const silentLogger = { debug: ()
|
|
130
|
+
const silentLogger = { debug: vi.fn() };
|
|
104
131
|
|
|
105
132
|
describe('checkAccessWithPairing', () => {
|
|
106
|
-
it('
|
|
133
|
+
it('defaults to webhookUser when dmPolicy is omitted', async () => {
|
|
107
134
|
const runtime = makeMockRuntime();
|
|
135
|
+
|
|
108
136
|
const result = await checkAccessWithPairing({
|
|
109
|
-
senderId: '
|
|
110
|
-
config: {
|
|
137
|
+
senderId: '42',
|
|
138
|
+
config: { webhookUrl: 'https://test.bitrix24.com/rest/42/token/' },
|
|
111
139
|
runtime,
|
|
112
140
|
accountId: 'default',
|
|
113
141
|
pairingAdapter: mockAdapter,
|
|
114
142
|
sendReply: vi.fn(),
|
|
115
143
|
logger: silentLogger,
|
|
116
144
|
});
|
|
145
|
+
|
|
117
146
|
expect(result).toBe('allow');
|
|
118
|
-
// Should NOT read store for open policy
|
|
119
147
|
expect(runtime.channel.pairing.readAllowFromStore).not.toHaveBeenCalled();
|
|
120
148
|
});
|
|
121
149
|
|
|
122
|
-
it('
|
|
150
|
+
it('allows only the webhook owner in webhookUser mode', async () => {
|
|
123
151
|
const runtime = makeMockRuntime();
|
|
152
|
+
const sendReply = vi.fn();
|
|
153
|
+
|
|
124
154
|
const result = await checkAccessWithPairing({
|
|
125
155
|
senderId: '42',
|
|
126
|
-
config: {
|
|
156
|
+
config: {
|
|
157
|
+
dmPolicy: 'webhookUser',
|
|
158
|
+
webhookUrl: 'https://test.bitrix24.com/rest/42/token/',
|
|
159
|
+
},
|
|
127
160
|
runtime,
|
|
128
161
|
accountId: 'default',
|
|
129
162
|
pairingAdapter: mockAdapter,
|
|
130
|
-
sendReply
|
|
163
|
+
sendReply,
|
|
131
164
|
logger: silentLogger,
|
|
132
165
|
});
|
|
166
|
+
|
|
133
167
|
expect(result).toBe('allow');
|
|
168
|
+
expect(runtime.channel.pairing.readAllowFromStore).not.toHaveBeenCalled();
|
|
169
|
+
expect(runtime.channel.pairing.upsertPairingRequest).not.toHaveBeenCalled();
|
|
170
|
+
expect(sendReply).not.toHaveBeenCalled();
|
|
134
171
|
});
|
|
135
172
|
|
|
136
|
-
it('
|
|
173
|
+
it('keeps senderId as the identity in webhookUser mode even when dialogId differs', async () => {
|
|
174
|
+
const runtime = makeMockRuntime();
|
|
175
|
+
|
|
176
|
+
const allowed = await checkAccessWithPairing({
|
|
177
|
+
senderId: '42',
|
|
178
|
+
dialogId: '2386',
|
|
179
|
+
isDirect: true,
|
|
180
|
+
config: {
|
|
181
|
+
dmPolicy: 'webhookUser',
|
|
182
|
+
webhookUrl: 'https://test.bitrix24.com/rest/42/token/',
|
|
183
|
+
},
|
|
184
|
+
runtime,
|
|
185
|
+
accountId: 'default',
|
|
186
|
+
pairingAdapter: mockAdapter,
|
|
187
|
+
sendReply: vi.fn(),
|
|
188
|
+
logger: silentLogger,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const denied = await checkAccessWithPairing({
|
|
192
|
+
senderId: '77',
|
|
193
|
+
dialogId: '42',
|
|
194
|
+
isDirect: true,
|
|
195
|
+
config: {
|
|
196
|
+
dmPolicy: 'webhookUser',
|
|
197
|
+
webhookUrl: 'https://test.bitrix24.com/rest/42/token/',
|
|
198
|
+
},
|
|
199
|
+
runtime,
|
|
200
|
+
accountId: 'default',
|
|
201
|
+
pairingAdapter: mockAdapter,
|
|
202
|
+
sendReply: vi.fn(),
|
|
203
|
+
logger: silentLogger,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
expect(allowed).toBe('allow');
|
|
207
|
+
expect(denied).toBe('deny');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('denies non-owner users in webhookUser mode', async () => {
|
|
211
|
+
const runtime = makeMockRuntime();
|
|
212
|
+
const sendReply = vi.fn();
|
|
213
|
+
|
|
214
|
+
const result = await checkAccessWithPairing({
|
|
215
|
+
senderId: '77',
|
|
216
|
+
config: {
|
|
217
|
+
dmPolicy: 'webhookUser',
|
|
218
|
+
webhookUrl: 'https://test.bitrix24.com/rest/42/token/',
|
|
219
|
+
},
|
|
220
|
+
runtime,
|
|
221
|
+
accountId: 'default',
|
|
222
|
+
pairingAdapter: mockAdapter,
|
|
223
|
+
sendReply,
|
|
224
|
+
logger: silentLogger,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
expect(result).toBe('deny');
|
|
228
|
+
expect(runtime.channel.pairing.readAllowFromStore).not.toHaveBeenCalled();
|
|
229
|
+
expect(runtime.channel.pairing.upsertPairingRequest).not.toHaveBeenCalled();
|
|
230
|
+
expect(sendReply).not.toHaveBeenCalled();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('returns allow when sender is in pairing store', async () => {
|
|
137
234
|
const runtime = makeMockRuntime(['42']);
|
|
235
|
+
|
|
138
236
|
const result = await checkAccessWithPairing({
|
|
139
237
|
senderId: '42',
|
|
140
238
|
config: { dmPolicy: 'pairing' },
|
|
@@ -144,40 +242,68 @@ describe('checkAccessWithPairing', () => {
|
|
|
144
242
|
sendReply: vi.fn(),
|
|
145
243
|
logger: silentLogger,
|
|
146
244
|
});
|
|
245
|
+
|
|
147
246
|
expect(result).toBe('allow');
|
|
148
247
|
});
|
|
149
248
|
|
|
150
|
-
it('
|
|
151
|
-
const runtime = makeMockRuntime(
|
|
249
|
+
it('returns allow when sender is in config allowFrom', async () => {
|
|
250
|
+
const runtime = makeMockRuntime();
|
|
251
|
+
|
|
152
252
|
const result = await checkAccessWithPairing({
|
|
153
|
-
senderId: '
|
|
154
|
-
config: {
|
|
253
|
+
senderId: '42',
|
|
254
|
+
config: {
|
|
255
|
+
dmPolicy: 'pairing',
|
|
256
|
+
allowFrom: ['bitrix24:42'],
|
|
257
|
+
},
|
|
155
258
|
runtime,
|
|
156
259
|
accountId: 'default',
|
|
157
260
|
pairingAdapter: mockAdapter,
|
|
158
261
|
sendReply: vi.fn(),
|
|
159
262
|
logger: silentLogger,
|
|
160
263
|
});
|
|
264
|
+
|
|
161
265
|
expect(result).toBe('allow');
|
|
266
|
+
expect(runtime.channel.pairing.upsertPairingRequest).not.toHaveBeenCalled();
|
|
162
267
|
});
|
|
163
268
|
|
|
164
|
-
it('
|
|
269
|
+
it('uses senderId as the pairing identity even when direct dialogId differs', async () => {
|
|
165
270
|
const runtime = makeMockRuntime(['42']);
|
|
271
|
+
|
|
166
272
|
const result = await checkAccessWithPairing({
|
|
167
|
-
senderId: '
|
|
168
|
-
|
|
273
|
+
senderId: '42',
|
|
274
|
+
dialogId: '42',
|
|
275
|
+
isDirect: true,
|
|
276
|
+
config: { dmPolicy: 'pairing' },
|
|
169
277
|
runtime,
|
|
170
278
|
accountId: 'default',
|
|
171
279
|
pairingAdapter: mockAdapter,
|
|
172
280
|
sendReply: vi.fn(),
|
|
173
281
|
logger: silentLogger,
|
|
174
282
|
});
|
|
175
|
-
|
|
283
|
+
|
|
284
|
+
expect(result).toBe('allow');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('normalizes prefixed entries from pairing store', async () => {
|
|
288
|
+
const runtime = makeMockRuntime(['b24:42']);
|
|
289
|
+
|
|
290
|
+
const result = await checkAccessWithPairing({
|
|
291
|
+
senderId: '42',
|
|
292
|
+
config: { dmPolicy: 'pairing' },
|
|
293
|
+
runtime,
|
|
294
|
+
accountId: 'default',
|
|
295
|
+
pairingAdapter: mockAdapter,
|
|
296
|
+
sendReply: vi.fn(),
|
|
297
|
+
logger: silentLogger,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
expect(result).toBe('allow');
|
|
176
301
|
});
|
|
177
302
|
|
|
178
303
|
it('upserts pairing request and sends reply for new pairing', async () => {
|
|
179
304
|
const runtime = makeMockRuntime();
|
|
180
305
|
const sendReply = vi.fn();
|
|
306
|
+
|
|
181
307
|
const result = await checkAccessWithPairing({
|
|
182
308
|
senderId: '77',
|
|
183
309
|
config: { dmPolicy: 'pairing' },
|
|
@@ -187,7 +313,9 @@ describe('checkAccessWithPairing', () => {
|
|
|
187
313
|
sendReply,
|
|
188
314
|
logger: silentLogger,
|
|
189
315
|
});
|
|
316
|
+
|
|
190
317
|
expect(result).toBe('pairing');
|
|
318
|
+
expect(runtime.channel.pairing.readAllowFromStore).toHaveBeenCalledWith('bitrix24', '', 'default');
|
|
191
319
|
expect(runtime.channel.pairing.upsertPairingRequest).toHaveBeenCalledWith({
|
|
192
320
|
channel: 'bitrix24',
|
|
193
321
|
id: '77',
|
|
@@ -210,6 +338,7 @@ describe('checkAccessWithPairing', () => {
|
|
|
210
338
|
created: false,
|
|
211
339
|
});
|
|
212
340
|
const sendReply = vi.fn();
|
|
341
|
+
|
|
213
342
|
const result = await checkAccessWithPairing({
|
|
214
343
|
senderId: '77',
|
|
215
344
|
config: { dmPolicy: 'pairing' },
|
|
@@ -219,21 +348,8 @@ describe('checkAccessWithPairing', () => {
|
|
|
219
348
|
sendReply,
|
|
220
349
|
logger: silentLogger,
|
|
221
350
|
});
|
|
351
|
+
|
|
222
352
|
expect(result).toBe('pairing');
|
|
223
353
|
expect(sendReply).not.toHaveBeenCalled();
|
|
224
354
|
});
|
|
225
|
-
|
|
226
|
-
it('normalizes config allowFrom entries with prefixes', async () => {
|
|
227
|
-
const runtime = makeMockRuntime();
|
|
228
|
-
const result = await checkAccessWithPairing({
|
|
229
|
-
senderId: '42',
|
|
230
|
-
config: { dmPolicy: 'pairing', allowFrom: ['b24:42'] },
|
|
231
|
-
runtime,
|
|
232
|
-
accountId: 'default',
|
|
233
|
-
pairingAdapter: mockAdapter,
|
|
234
|
-
sendReply: vi.fn(),
|
|
235
|
-
logger: silentLogger,
|
|
236
|
-
});
|
|
237
|
-
expect(result).toBe('allow');
|
|
238
|
-
});
|
|
239
355
|
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
+
import { Bitrix24Api } from '../src/api.js';
|
|
3
|
+
import { Bitrix24ApiError } from '../src/utils.js';
|
|
4
|
+
|
|
5
|
+
const silentLogger = {
|
|
6
|
+
info: vi.fn(),
|
|
7
|
+
warn: vi.fn(),
|
|
8
|
+
error: vi.fn(),
|
|
9
|
+
debug: vi.fn(),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
describe('Bitrix24Api', () => {
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
vi.useRealTimers();
|
|
15
|
+
vi.restoreAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('calls webhook with a request timeout signal', async () => {
|
|
19
|
+
const api = new Bitrix24Api({ logger: silentLogger });
|
|
20
|
+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce(
|
|
21
|
+
new Response(JSON.stringify({ result: { ok: true } }), { status: 200 }),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const result = await api.callWebhook(
|
|
25
|
+
'https://test.bitrix24.com/rest/1/token/',
|
|
26
|
+
'profile',
|
|
27
|
+
{ foo: 'bar' },
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
expect(result).toEqual({ result: { ok: true } });
|
|
31
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
32
|
+
'https://test.bitrix24.com/rest/1/token/profile.json',
|
|
33
|
+
expect.objectContaining({
|
|
34
|
+
method: 'POST',
|
|
35
|
+
body: JSON.stringify({ foo: 'bar' }),
|
|
36
|
+
signal: expect.any(AbortSignal),
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('maps timeout errors to Bitrix24ApiError', async () => {
|
|
42
|
+
const api = new Bitrix24Api({ logger: silentLogger });
|
|
43
|
+
vi.useFakeTimers();
|
|
44
|
+
vi.spyOn(globalThis, 'fetch').mockRejectedValue(
|
|
45
|
+
Object.assign(new Error('timed out'), { name: 'TimeoutError' }),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const assertion = expect(
|
|
49
|
+
api.callWebhook('https://test.bitrix24.com/rest/1/token/', 'profile'),
|
|
50
|
+
).rejects.toMatchObject({
|
|
51
|
+
name: 'Bitrix24ApiError',
|
|
52
|
+
code: 'TIMEOUT',
|
|
53
|
+
});
|
|
54
|
+
await vi.runAllTimersAsync();
|
|
55
|
+
await assertion;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('retries transient network fetch failures', async () => {
|
|
59
|
+
const api = new Bitrix24Api({ logger: silentLogger });
|
|
60
|
+
vi.useFakeTimers();
|
|
61
|
+
const fetchSpy = vi.spyOn(globalThis, 'fetch')
|
|
62
|
+
.mockRejectedValueOnce(Object.assign(new TypeError('fetch failed'), {
|
|
63
|
+
cause: { code: 'ECONNRESET' },
|
|
64
|
+
}))
|
|
65
|
+
.mockResolvedValueOnce(
|
|
66
|
+
new Response(JSON.stringify({ result: { ok: true } }), { status: 200 }),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const assertion = expect(api.callWebhook(
|
|
70
|
+
'https://test.bitrix24.com/rest/1/token/',
|
|
71
|
+
'profile',
|
|
72
|
+
{ foo: 'bar' },
|
|
73
|
+
)).resolves.toEqual({ result: { ok: true } });
|
|
74
|
+
await vi.runAllTimersAsync();
|
|
75
|
+
await assertion;
|
|
76
|
+
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('throws INVALID_RESPONSE when sendMessage result has no valid id', async () => {
|
|
80
|
+
const api = new Bitrix24Api({ logger: silentLogger });
|
|
81
|
+
vi.spyOn(api, 'callWebhook').mockResolvedValueOnce({
|
|
82
|
+
result: {} as never,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
await expect(api.sendMessage(
|
|
86
|
+
'https://test.bitrix24.com/rest/1/token/',
|
|
87
|
+
{ botId: 7, botToken: 'bot_token' },
|
|
88
|
+
'42',
|
|
89
|
+
'hello',
|
|
90
|
+
)).rejects.toMatchObject({
|
|
91
|
+
name: 'Bitrix24ApiError',
|
|
92
|
+
code: 'INVALID_RESPONSE',
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|