@ihazz/bitrix24 0.2.4 → 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/README.md +118 -164
- package/index.ts +46 -11
- package/openclaw.plugin.json +1 -0
- package/package.json +1 -1
- package/skills/bitrix24/SKILL.md +70 -0
- package/src/access-control.ts +102 -46
- package/src/api.ts +434 -232
- package/src/channel.ts +1486 -393
- 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 +279 -61
- 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
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { SendService } from '../src/send-service.js';
|
|
3
|
+
import type { Bitrix24Api } from '../src/api.js';
|
|
4
|
+
|
|
5
|
+
const silentLogger = {
|
|
6
|
+
info: vi.fn(),
|
|
7
|
+
warn: vi.fn(),
|
|
8
|
+
error: vi.fn(),
|
|
9
|
+
debug: vi.fn(),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function mockApi(): Bitrix24Api {
|
|
13
|
+
return {
|
|
14
|
+
answerCommand: vi.fn().mockResolvedValue(true),
|
|
15
|
+
notifyInputAction: vi.fn().mockResolvedValue(true),
|
|
16
|
+
sendTyping: vi.fn().mockResolvedValue(true),
|
|
17
|
+
readMessage: vi.fn().mockResolvedValue({
|
|
18
|
+
chatId: 1,
|
|
19
|
+
lastId: 123,
|
|
20
|
+
counter: 0,
|
|
21
|
+
viewedMessages: [123],
|
|
22
|
+
}),
|
|
23
|
+
sendMessage: vi.fn(),
|
|
24
|
+
updateMessage: vi.fn(),
|
|
25
|
+
} as unknown as Bitrix24Api;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('SendService', () => {
|
|
29
|
+
let api: Bitrix24Api;
|
|
30
|
+
let service: SendService;
|
|
31
|
+
|
|
32
|
+
const ctx = {
|
|
33
|
+
webhookUrl: 'https://test.bitrix24.com/rest/1/token/',
|
|
34
|
+
bot: { botId: 7, botToken: 'bot_token' },
|
|
35
|
+
dialogId: '42',
|
|
36
|
+
};
|
|
37
|
+
const commandCtx = {
|
|
38
|
+
...ctx,
|
|
39
|
+
commandId: 55,
|
|
40
|
+
messageId: 123,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
api = mockApi();
|
|
45
|
+
service = new SendService(api, silentLogger);
|
|
46
|
+
vi.clearAllMocks();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('sends explicit input action status', async () => {
|
|
50
|
+
await service.sendStatus(ctx, 'IMBOT_AGENT_ACTION_COMPOSING', 45);
|
|
51
|
+
|
|
52
|
+
expect(api.notifyInputAction).toHaveBeenCalledWith(
|
|
53
|
+
ctx.webhookUrl,
|
|
54
|
+
ctx.bot,
|
|
55
|
+
ctx.dialogId,
|
|
56
|
+
{
|
|
57
|
+
statusMessageCode: 'IMBOT_AGENT_ACTION_COMPOSING',
|
|
58
|
+
duration: 45,
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('uses notifyInputAction for sendTyping', async () => {
|
|
64
|
+
await service.sendTyping(ctx);
|
|
65
|
+
|
|
66
|
+
expect(api.notifyInputAction).toHaveBeenCalledWith(
|
|
67
|
+
ctx.webhookUrl,
|
|
68
|
+
ctx.bot,
|
|
69
|
+
ctx.dialogId,
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('marks a specific message as read', async () => {
|
|
74
|
+
await service.markRead(ctx, 123);
|
|
75
|
+
|
|
76
|
+
expect(api.readMessage).toHaveBeenCalledWith(
|
|
77
|
+
ctx.webhookUrl,
|
|
78
|
+
ctx.bot,
|
|
79
|
+
ctx.dialogId,
|
|
80
|
+
123,
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('swallows read failures', async () => {
|
|
85
|
+
(api.readMessage as ReturnType<typeof vi.fn>).mockRejectedValueOnce(new Error('read failed'));
|
|
86
|
+
|
|
87
|
+
await expect(service.markRead(ctx, 123)).resolves.toBeUndefined();
|
|
88
|
+
expect(silentLogger.debug).toHaveBeenCalled();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('answers native commands via imbot.v2.Command.answer', async () => {
|
|
92
|
+
await service.answerCommandText(commandCtx, 'Status ok');
|
|
93
|
+
|
|
94
|
+
expect(api.answerCommand).toHaveBeenCalledWith(
|
|
95
|
+
ctx.webhookUrl,
|
|
96
|
+
ctx.bot,
|
|
97
|
+
55,
|
|
98
|
+
123,
|
|
99
|
+
ctx.dialogId,
|
|
100
|
+
'Status ok',
|
|
101
|
+
undefined,
|
|
102
|
+
);
|
|
103
|
+
expect(api.sendMessage).not.toHaveBeenCalled();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('sends command follow-up chunks as regular chat messages', async () => {
|
|
107
|
+
(api.sendMessage as ReturnType<typeof vi.fn>).mockResolvedValueOnce(777);
|
|
108
|
+
const longText = `${'A'.repeat(20000)}${'B'.repeat(50)}`;
|
|
109
|
+
|
|
110
|
+
const result = await service.answerCommandText(commandCtx, longText);
|
|
111
|
+
|
|
112
|
+
expect(result.ok).toBe(true);
|
|
113
|
+
expect(api.answerCommand).toHaveBeenCalledOnce();
|
|
114
|
+
expect(api.sendMessage).toHaveBeenCalledOnce();
|
|
115
|
+
expect(api.sendMessage).toHaveBeenCalledWith(
|
|
116
|
+
ctx.webhookUrl,
|
|
117
|
+
ctx.bot,
|
|
118
|
+
ctx.dialogId,
|
|
119
|
+
'B'.repeat(50),
|
|
120
|
+
undefined,
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('throws when sendText cannot deliver a chunk', async () => {
|
|
125
|
+
(api.sendMessage as ReturnType<typeof vi.fn>).mockRejectedValueOnce(new Error('send failed'));
|
|
126
|
+
|
|
127
|
+
await expect(service.sendText(ctx, 'Hello')).rejects.toThrow('send failed');
|
|
128
|
+
expect(silentLogger.error).toHaveBeenCalled();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('throws when command answer fails on the first chunk', async () => {
|
|
132
|
+
(api.answerCommand as ReturnType<typeof vi.fn>).mockRejectedValueOnce(new Error('answer failed'));
|
|
133
|
+
|
|
134
|
+
await expect(service.answerCommandText(commandCtx, 'Status ok')).rejects.toThrow('answer failed');
|
|
135
|
+
expect(silentLogger.error).toHaveBeenCalled();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('throws when command follow-up chunk cannot be sent', async () => {
|
|
139
|
+
(api.sendMessage as ReturnType<typeof vi.fn>).mockRejectedValueOnce(new Error('follow-up failed'));
|
|
140
|
+
const longText = `${'A'.repeat(20000)}${'B'.repeat(50)}`;
|
|
141
|
+
|
|
142
|
+
await expect(service.answerCommandText(commandCtx, longText)).rejects.toThrow('follow-up failed');
|
|
143
|
+
expect(silentLogger.error).toHaveBeenCalled();
|
|
144
|
+
});
|
|
145
|
+
});
|