@ai-devkit/channel-connector 0.13.3 → 0.14.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 +1 -1
- package/dist/ChannelConfigRepository.d.ts +1 -1
- package/dist/ChannelConfigRepository.d.ts.map +1 -1
- package/dist/ChannelConfigRepository.js +13 -13
- package/dist/ChannelConfigRepository.js.map +1 -1
- package/dist/ChannelManager.d.ts +1 -1
- package/dist/ChannelManager.d.ts.map +1 -1
- package/dist/ChannelManager.js +9 -9
- package/dist/ChannelManager.js.map +1 -1
- package/dist/__tests__/ChannelConfigRepository.test.js +72 -72
- package/dist/__tests__/ChannelConfigRepository.test.js.map +1 -1
- package/dist/__tests__/ChannelManager.test.js +26 -26
- package/dist/__tests__/ChannelManager.test.js.map +1 -1
- package/dist/__tests__/adapters/slack/SlackAdapter.test.js +137 -137
- package/dist/__tests__/adapters/slack/SlackAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/slack/SlackMessageDelivery.test.js +36 -36
- package/dist/__tests__/adapters/slack/SlackMessageDelivery.test.js.map +1 -1
- package/dist/__tests__/adapters/slack/slackMarkdown.test.js +25 -25
- package/dist/__tests__/adapters/slack/slackMarkdown.test.js.map +1 -1
- package/dist/__tests__/adapters/slack/slackQuestion.test.js +41 -41
- package/dist/__tests__/adapters/slack/slackQuestion.test.js.map +1 -1
- package/dist/__tests__/adapters/telegram/TelegramAdapter.test.js +177 -154
- package/dist/__tests__/adapters/telegram/TelegramAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/telegram/telegramHtml.test.js +109 -109
- package/dist/__tests__/adapters/telegram/telegramHtml.test.js.map +1 -1
- package/dist/adapters/ChannelAdapter.d.ts +1 -1
- package/dist/adapters/ChannelAdapter.d.ts.map +1 -1
- package/dist/adapters/ChannelAdapter.js +1 -1
- package/dist/adapters/ChannelAdapter.js.map +1 -1
- package/dist/adapters/slack/SlackAdapter.d.ts +2 -2
- package/dist/adapters/slack/SlackAdapter.d.ts.map +1 -1
- package/dist/adapters/slack/SlackAdapter.js +17 -17
- package/dist/adapters/slack/SlackAdapter.js.map +1 -1
- package/dist/adapters/slack/SlackMessageDelivery.d.ts +1 -1
- package/dist/adapters/slack/SlackMessageDelivery.d.ts.map +1 -1
- package/dist/adapters/slack/SlackMessageDelivery.js +6 -6
- package/dist/adapters/slack/SlackMessageDelivery.js.map +1 -1
- package/dist/adapters/slack/slackMarkdown.d.ts.map +1 -1
- package/dist/adapters/slack/slackMarkdown.js +36 -36
- package/dist/adapters/slack/slackMarkdown.js.map +1 -1
- package/dist/adapters/slack/slackQuestion.d.ts +7 -7
- package/dist/adapters/slack/slackQuestion.d.ts.map +1 -1
- package/dist/adapters/slack/slackQuestion.js +13 -13
- package/dist/adapters/slack/slackQuestion.js.map +1 -1
- package/dist/adapters/telegram/TelegramAdapter.d.ts +12 -2
- package/dist/adapters/telegram/TelegramAdapter.d.ts.map +1 -1
- package/dist/adapters/telegram/TelegramAdapter.js +38 -27
- package/dist/adapters/telegram/TelegramAdapter.js.map +1 -1
- package/dist/adapters/telegram/TelegramMessageDelivery.d.ts.map +1 -1
- package/dist/adapters/telegram/TelegramMessageDelivery.js +2 -2
- package/dist/adapters/telegram/TelegramMessageDelivery.js.map +1 -1
- package/dist/adapters/telegram/telegramHtml.d.ts.map +1 -1
- package/dist/adapters/telegram/telegramHtml.js +16 -16
- package/dist/adapters/telegram/telegramHtml.js.map +1 -1
- package/dist/adapters/telegram/telegramKeyboard.d.ts +1 -1
- package/dist/adapters/telegram/telegramKeyboard.d.ts.map +1 -1
- package/dist/adapters/telegram/telegramKeyboard.js.map +1 -1
- package/dist/adapters/telegram/telegramMarkdown.d.ts.map +1 -1
- package/dist/adapters/telegram/telegramMarkdown.js +28 -28
- package/dist/adapters/telegram/telegramMarkdown.js.map +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +6 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +21 -22
- package/vitest.config.ts +16 -16
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
import { EventEmitter } from
|
|
2
|
-
import { SlackAdapter, validateSlackAppToken, validateSlackCredentials } from
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { SlackAdapter, validateSlackAppToken, validateSlackCredentials } from "../../../adapters/slack/SlackAdapter.js";
|
|
3
3
|
let FakeSocketClient = class FakeSocketClient extends EventEmitter {
|
|
4
4
|
start = vi.fn().mockResolvedValue(undefined);
|
|
5
5
|
disconnect = vi.fn().mockResolvedValue(undefined);
|
|
6
6
|
};
|
|
7
7
|
const config = {
|
|
8
|
-
appToken:
|
|
9
|
-
botToken:
|
|
10
|
-
appId:
|
|
11
|
-
botUserId:
|
|
12
|
-
workspaceId:
|
|
13
|
-
transport:
|
|
14
|
-
audience:
|
|
8
|
+
appToken: "xapp-fake",
|
|
9
|
+
botToken: "xoxb-fake",
|
|
10
|
+
appId: "A123",
|
|
11
|
+
botUserId: "U-BOT",
|
|
12
|
+
workspaceId: "T123",
|
|
13
|
+
transport: "socket-mode",
|
|
14
|
+
audience: "dm"
|
|
15
15
|
};
|
|
16
16
|
function envelope(overrides = {}) {
|
|
17
17
|
return {
|
|
18
|
-
type:
|
|
18
|
+
type: "events_api",
|
|
19
19
|
ack: vi.fn().mockResolvedValue(undefined),
|
|
20
20
|
body: {
|
|
21
|
-
team_id:
|
|
22
|
-
event_id:
|
|
21
|
+
team_id: "T123",
|
|
22
|
+
event_id: "Ev123",
|
|
23
23
|
event: {
|
|
24
|
-
type:
|
|
25
|
-
channel_type:
|
|
26
|
-
channel:
|
|
27
|
-
user:
|
|
28
|
-
text:
|
|
29
|
-
ts:
|
|
30
|
-
event_ts:
|
|
24
|
+
type: "message",
|
|
25
|
+
channel_type: "im",
|
|
26
|
+
channel: "D123",
|
|
27
|
+
user: "U123",
|
|
28
|
+
text: "run tests",
|
|
29
|
+
ts: "100.1",
|
|
30
|
+
event_ts: "100.1",
|
|
31
31
|
...overrides
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
describe(
|
|
37
|
-
it(
|
|
36
|
+
describe("SlackAdapter", ()=>{
|
|
37
|
+
it("validates bot identity without returning either credential", async ()=>{
|
|
38
38
|
const authTest = vi.fn().mockResolvedValue({
|
|
39
39
|
ok: true,
|
|
40
|
-
app_id:
|
|
41
|
-
user_id:
|
|
42
|
-
team_id:
|
|
43
|
-
team:
|
|
40
|
+
app_id: "A123",
|
|
41
|
+
user_id: "U-BOT",
|
|
42
|
+
team_id: "T123",
|
|
43
|
+
team: "Sandbox"
|
|
44
44
|
});
|
|
45
|
-
await expect(validateSlackCredentials(
|
|
45
|
+
await expect(validateSlackCredentials("xoxb-fake", {
|
|
46
46
|
authTest
|
|
47
47
|
})).resolves.toEqual({
|
|
48
|
-
appId:
|
|
49
|
-
botUserId:
|
|
50
|
-
workspaceId:
|
|
51
|
-
workspaceName:
|
|
48
|
+
appId: "A123",
|
|
49
|
+
botUserId: "U-BOT",
|
|
50
|
+
workspaceId: "T123",
|
|
51
|
+
workspaceName: "Sandbox"
|
|
52
52
|
});
|
|
53
53
|
});
|
|
54
|
-
it(
|
|
54
|
+
it("accepts Slack documented bot identity without an app ID", async ()=>{
|
|
55
55
|
const authTest = vi.fn().mockResolvedValue({
|
|
56
56
|
ok: true,
|
|
57
|
-
bot_id:
|
|
58
|
-
user_id:
|
|
59
|
-
team_id:
|
|
60
|
-
team:
|
|
57
|
+
bot_id: "B123",
|
|
58
|
+
user_id: "U-BOT",
|
|
59
|
+
team_id: "T123",
|
|
60
|
+
team: "Sandbox"
|
|
61
61
|
});
|
|
62
|
-
await expect(validateSlackCredentials(
|
|
62
|
+
await expect(validateSlackCredentials("xoxb-fake", {
|
|
63
63
|
authTest
|
|
64
64
|
})).resolves.toEqual({
|
|
65
|
-
botUserId:
|
|
66
|
-
workspaceId:
|
|
67
|
-
workspaceName:
|
|
65
|
+
botUserId: "U-BOT",
|
|
66
|
+
workspaceId: "T123",
|
|
67
|
+
workspaceName: "Sandbox"
|
|
68
68
|
});
|
|
69
69
|
});
|
|
70
|
-
it(
|
|
70
|
+
it("rejects incomplete Slack credential identity", async ()=>{
|
|
71
71
|
const authTest = vi.fn().mockResolvedValue({
|
|
72
72
|
ok: true,
|
|
73
|
-
team_id:
|
|
73
|
+
team_id: "T123"
|
|
74
74
|
});
|
|
75
|
-
await expect(validateSlackCredentials(
|
|
75
|
+
await expect(validateSlackCredentials("xoxb-fake", {
|
|
76
76
|
authTest
|
|
77
|
-
})).rejects.toThrow(
|
|
77
|
+
})).rejects.toThrow("Slack bot token returned incomplete identity");
|
|
78
78
|
});
|
|
79
|
-
it(
|
|
79
|
+
it("validates an app token through apps.connections.open", async ()=>{
|
|
80
80
|
const open = vi.fn().mockResolvedValue({
|
|
81
81
|
ok: true,
|
|
82
|
-
url:
|
|
82
|
+
url: "wss://wss-primary.slack.com/link/"
|
|
83
83
|
});
|
|
84
|
-
await expect(validateSlackAppToken(
|
|
84
|
+
await expect(validateSlackAppToken("xapp-fake", {
|
|
85
85
|
open
|
|
86
86
|
})).resolves.toBeUndefined();
|
|
87
87
|
expect(open).toHaveBeenCalledOnce();
|
|
88
88
|
});
|
|
89
|
-
it(
|
|
89
|
+
it("rejects an app token that cannot open Socket Mode", async ()=>{
|
|
90
90
|
const open = vi.fn().mockResolvedValue({
|
|
91
91
|
ok: false
|
|
92
92
|
});
|
|
93
|
-
await expect(validateSlackAppToken(
|
|
93
|
+
await expect(validateSlackAppToken("xapp-fake", {
|
|
94
94
|
open
|
|
95
|
-
})).rejects.toThrow(
|
|
95
|
+
})).rejects.toThrow("Slack app token cannot open Socket Mode");
|
|
96
96
|
});
|
|
97
|
-
it(
|
|
97
|
+
it("starts and stops the official Socket Mode client and reports health", async ()=>{
|
|
98
98
|
const socket = new FakeSocketClient();
|
|
99
99
|
const adapter = new SlackAdapter(config, {
|
|
100
100
|
socketClient: socket,
|
|
@@ -112,7 +112,7 @@ describe('SlackAdapter', ()=>{
|
|
|
112
112
|
expect(socket.disconnect).toHaveBeenCalledOnce();
|
|
113
113
|
expect(await adapter.isHealthy()).toBe(false);
|
|
114
114
|
});
|
|
115
|
-
it(
|
|
115
|
+
it("does not register duplicate listeners across restarts", async ()=>{
|
|
116
116
|
const socket = new FakeSocketClient();
|
|
117
117
|
const adapter = new SlackAdapter(config, {
|
|
118
118
|
socketClient: socket,
|
|
@@ -125,14 +125,14 @@ describe('SlackAdapter', ()=>{
|
|
|
125
125
|
await adapter.start();
|
|
126
126
|
await adapter.stop();
|
|
127
127
|
await adapter.start();
|
|
128
|
-
expect(socket.listenerCount(
|
|
129
|
-
expect(socket.listenerCount(
|
|
130
|
-
socket.emit(
|
|
128
|
+
expect(socket.listenerCount("slack_event")).toBe(1);
|
|
129
|
+
expect(socket.listenerCount("interactive")).toBe(1);
|
|
130
|
+
socket.emit("disconnected");
|
|
131
131
|
expect(await adapter.isHealthy()).toBe(false);
|
|
132
|
-
socket.emit(
|
|
132
|
+
socket.emit("connected");
|
|
133
133
|
expect(await adapter.isHealthy()).toBe(true);
|
|
134
134
|
});
|
|
135
|
-
it(
|
|
135
|
+
it("acknowledges before delivering one normalized workspace DM event", async ()=>{
|
|
136
136
|
const socket = new FakeSocketClient();
|
|
137
137
|
const adapter = new SlackAdapter(config, {
|
|
138
138
|
socketClient: socket,
|
|
@@ -144,71 +144,71 @@ describe('SlackAdapter', ()=>{
|
|
|
144
144
|
});
|
|
145
145
|
const order = [];
|
|
146
146
|
adapter.onMessage(vi.fn(async (message)=>{
|
|
147
|
-
order.push(
|
|
147
|
+
order.push("handler");
|
|
148
148
|
expect(message).toMatchObject({
|
|
149
|
-
channelType:
|
|
150
|
-
chatId:
|
|
151
|
-
userId:
|
|
152
|
-
workspaceId:
|
|
153
|
-
messageId:
|
|
154
|
-
text:
|
|
149
|
+
channelType: "slack",
|
|
150
|
+
chatId: "D123",
|
|
151
|
+
userId: "U123",
|
|
152
|
+
workspaceId: "T123",
|
|
153
|
+
messageId: "Ev123",
|
|
154
|
+
text: "run tests"
|
|
155
155
|
});
|
|
156
156
|
}));
|
|
157
157
|
await adapter.start();
|
|
158
158
|
const item = envelope();
|
|
159
159
|
item.ack.mockImplementation(async ()=>{
|
|
160
|
-
order.push(
|
|
160
|
+
order.push("ack");
|
|
161
161
|
});
|
|
162
|
-
socket.emit(
|
|
162
|
+
socket.emit("slack_event", item);
|
|
163
163
|
await vi.waitFor(()=>expect(order).toEqual([
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
"ack",
|
|
165
|
+
"handler"
|
|
166
166
|
]));
|
|
167
|
-
socket.emit(
|
|
167
|
+
socket.emit("slack_event", item);
|
|
168
168
|
await new Promise((resolve)=>setTimeout(resolve, 0));
|
|
169
169
|
expect(order).toEqual([
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
"ack",
|
|
171
|
+
"handler",
|
|
172
|
+
"ack"
|
|
173
173
|
]);
|
|
174
174
|
});
|
|
175
175
|
it.each([
|
|
176
176
|
[
|
|
177
|
-
|
|
177
|
+
"wrong workspace",
|
|
178
178
|
{},
|
|
179
179
|
{
|
|
180
|
-
team_id:
|
|
180
|
+
team_id: "T999"
|
|
181
181
|
}
|
|
182
182
|
],
|
|
183
183
|
[
|
|
184
|
-
|
|
184
|
+
"bot message",
|
|
185
185
|
{
|
|
186
|
-
bot_id:
|
|
186
|
+
bot_id: "B123"
|
|
187
187
|
},
|
|
188
188
|
{}
|
|
189
189
|
],
|
|
190
190
|
[
|
|
191
|
-
|
|
191
|
+
"self message",
|
|
192
192
|
{
|
|
193
|
-
user:
|
|
193
|
+
user: "U-BOT"
|
|
194
194
|
},
|
|
195
195
|
{}
|
|
196
196
|
],
|
|
197
197
|
[
|
|
198
|
-
|
|
198
|
+
"subtype",
|
|
199
199
|
{
|
|
200
|
-
subtype:
|
|
200
|
+
subtype: "message_changed"
|
|
201
201
|
},
|
|
202
202
|
{}
|
|
203
203
|
],
|
|
204
204
|
[
|
|
205
|
-
|
|
205
|
+
"non-DM",
|
|
206
206
|
{
|
|
207
|
-
channel_type:
|
|
207
|
+
channel_type: "channel"
|
|
208
208
|
},
|
|
209
209
|
{}
|
|
210
210
|
]
|
|
211
|
-
])(
|
|
211
|
+
])("ignores %s events", async (_name, eventOverrides, bodyOverrides)=>{
|
|
212
212
|
const socket = new FakeSocketClient();
|
|
213
213
|
const handler = vi.fn();
|
|
214
214
|
const adapter = new SlackAdapter(config, {
|
|
@@ -223,11 +223,11 @@ describe('SlackAdapter', ()=>{
|
|
|
223
223
|
await adapter.start();
|
|
224
224
|
const item = envelope(eventOverrides);
|
|
225
225
|
Object.assign(item.body, bodyOverrides);
|
|
226
|
-
socket.emit(
|
|
226
|
+
socket.emit("slack_event", item);
|
|
227
227
|
await vi.waitFor(()=>expect(item.ack).toHaveBeenCalled());
|
|
228
228
|
expect(handler).not.toHaveBeenCalled();
|
|
229
229
|
});
|
|
230
|
-
it(
|
|
230
|
+
it("delivers an ordinary workspace DM immediately without pairing", async ()=>{
|
|
231
231
|
const socket = new FakeSocketClient();
|
|
232
232
|
const handler = vi.fn();
|
|
233
233
|
const adapter = new SlackAdapter(config, {
|
|
@@ -240,21 +240,21 @@ describe('SlackAdapter', ()=>{
|
|
|
240
240
|
});
|
|
241
241
|
adapter.onMessage(handler);
|
|
242
242
|
await adapter.start();
|
|
243
|
-
socket.emit(
|
|
244
|
-
text:
|
|
243
|
+
socket.emit("slack_event", envelope({
|
|
244
|
+
text: "run tests"
|
|
245
245
|
}));
|
|
246
246
|
await vi.waitFor(()=>expect(handler).toHaveBeenCalledWith(expect.objectContaining({
|
|
247
|
-
chatId:
|
|
248
|
-
userId:
|
|
249
|
-
text:
|
|
250
|
-
workspaceId:
|
|
247
|
+
chatId: "D123",
|
|
248
|
+
userId: "U123",
|
|
249
|
+
text: "run tests",
|
|
250
|
+
workspaceId: "T123"
|
|
251
251
|
})));
|
|
252
252
|
});
|
|
253
|
-
it(
|
|
253
|
+
it("sends an accessible Block Kit question and finalizes its actions", async ()=>{
|
|
254
254
|
const socket = new FakeSocketClient();
|
|
255
255
|
const postMessage = vi.fn().mockResolvedValue({
|
|
256
256
|
ok: true,
|
|
257
|
-
ts:
|
|
257
|
+
ts: "400.1"
|
|
258
258
|
});
|
|
259
259
|
const update = vi.fn().mockResolvedValue({
|
|
260
260
|
ok: true
|
|
@@ -268,37 +268,37 @@ describe('SlackAdapter', ()=>{
|
|
|
268
268
|
}
|
|
269
269
|
}
|
|
270
270
|
});
|
|
271
|
-
await expect(adapter.sendQuestion(
|
|
272
|
-
id:
|
|
273
|
-
header:
|
|
274
|
-
question:
|
|
271
|
+
await expect(adapter.sendQuestion("D123", {
|
|
272
|
+
id: "q1",
|
|
273
|
+
header: "Scope",
|
|
274
|
+
question: "Choose one",
|
|
275
275
|
allowSkip: true,
|
|
276
276
|
options: [
|
|
277
277
|
{
|
|
278
|
-
label:
|
|
279
|
-
description:
|
|
280
|
-
value:
|
|
278
|
+
label: "Safe",
|
|
279
|
+
description: "Read only",
|
|
280
|
+
value: "1"
|
|
281
281
|
}
|
|
282
282
|
]
|
|
283
283
|
})).resolves.toEqual({
|
|
284
|
-
messageId:
|
|
284
|
+
messageId: "400.1"
|
|
285
285
|
});
|
|
286
286
|
expect(postMessage).toHaveBeenCalledWith(expect.objectContaining({
|
|
287
|
-
channel:
|
|
288
|
-
text:
|
|
287
|
+
channel: "D123",
|
|
288
|
+
text: "Scope: Choose one",
|
|
289
289
|
blocks: expect.any(Array)
|
|
290
290
|
}));
|
|
291
|
-
await adapter.finalizeInteraction(
|
|
291
|
+
await adapter.finalizeInteraction("D123", "400.1");
|
|
292
292
|
expect(update).toHaveBeenCalledWith({
|
|
293
|
-
channel:
|
|
294
|
-
ts:
|
|
293
|
+
channel: "D123",
|
|
294
|
+
ts: "400.1",
|
|
295
295
|
blocks: []
|
|
296
296
|
});
|
|
297
297
|
});
|
|
298
|
-
it(
|
|
298
|
+
it("escapes Slack control syntax in question fallback text", async ()=>{
|
|
299
299
|
const postMessage = vi.fn().mockResolvedValue({
|
|
300
300
|
ok: true,
|
|
301
|
-
ts:
|
|
301
|
+
ts: "400.1"
|
|
302
302
|
});
|
|
303
303
|
const adapter = new SlackAdapter(config, {
|
|
304
304
|
socketClient: new FakeSocketClient(),
|
|
@@ -308,23 +308,23 @@ describe('SlackAdapter', ()=>{
|
|
|
308
308
|
}
|
|
309
309
|
}
|
|
310
310
|
});
|
|
311
|
-
await adapter.sendQuestion(
|
|
312
|
-
id:
|
|
313
|
-
header:
|
|
314
|
-
question:
|
|
311
|
+
await adapter.sendQuestion("D123", {
|
|
312
|
+
id: "q1",
|
|
313
|
+
header: "<!channel>",
|
|
314
|
+
question: "Choose <one> & continue",
|
|
315
315
|
allowSkip: false,
|
|
316
316
|
options: [
|
|
317
317
|
{
|
|
318
|
-
label:
|
|
319
|
-
value:
|
|
318
|
+
label: "Safe",
|
|
319
|
+
value: "1"
|
|
320
320
|
}
|
|
321
321
|
]
|
|
322
322
|
});
|
|
323
323
|
expect(postMessage).toHaveBeenCalledWith(expect.objectContaining({
|
|
324
|
-
text:
|
|
324
|
+
text: "<!channel>: Choose <one> & continue"
|
|
325
325
|
}));
|
|
326
326
|
});
|
|
327
|
-
it(
|
|
327
|
+
it("acknowledges and delivers one valid workspace block action", async ()=>{
|
|
328
328
|
const socket = new FakeSocketClient();
|
|
329
329
|
const handler = vi.fn();
|
|
330
330
|
const adapter = new SlackAdapter(config, {
|
|
@@ -340,44 +340,44 @@ describe('SlackAdapter', ()=>{
|
|
|
340
340
|
const item = {
|
|
341
341
|
ack: vi.fn().mockResolvedValue(undefined),
|
|
342
342
|
body: {
|
|
343
|
-
type:
|
|
343
|
+
type: "block_actions",
|
|
344
344
|
team: {
|
|
345
|
-
id:
|
|
345
|
+
id: "T123"
|
|
346
346
|
},
|
|
347
347
|
user: {
|
|
348
|
-
id:
|
|
348
|
+
id: "U123"
|
|
349
349
|
},
|
|
350
350
|
channel: {
|
|
351
|
-
id:
|
|
351
|
+
id: "D123"
|
|
352
352
|
},
|
|
353
353
|
container: {
|
|
354
|
-
message_ts:
|
|
354
|
+
message_ts: "400.1"
|
|
355
355
|
},
|
|
356
356
|
actions: [
|
|
357
357
|
{
|
|
358
|
-
action_id:
|
|
359
|
-
action_ts:
|
|
360
|
-
value:
|
|
358
|
+
action_id: "ai_devkit_question",
|
|
359
|
+
action_ts: "401.1",
|
|
360
|
+
value: "q1:1"
|
|
361
361
|
}
|
|
362
362
|
]
|
|
363
363
|
}
|
|
364
364
|
};
|
|
365
|
-
socket.emit(
|
|
365
|
+
socket.emit("interactive", item);
|
|
366
366
|
await vi.waitFor(()=>expect(handler).toHaveBeenCalledOnce());
|
|
367
367
|
expect(item.ack).toHaveBeenCalledOnce();
|
|
368
368
|
expect(handler).toHaveBeenCalledWith(expect.objectContaining({
|
|
369
|
-
workspaceId:
|
|
370
|
-
chatId:
|
|
371
|
-
userId:
|
|
372
|
-
messageId:
|
|
373
|
-
value:
|
|
369
|
+
workspaceId: "T123",
|
|
370
|
+
chatId: "D123",
|
|
371
|
+
userId: "U123",
|
|
372
|
+
messageId: "400.1",
|
|
373
|
+
value: "q1:1"
|
|
374
374
|
}));
|
|
375
|
-
socket.emit(
|
|
375
|
+
socket.emit("interactive", item);
|
|
376
376
|
await new Promise((resolve)=>setTimeout(resolve, 0));
|
|
377
377
|
expect(handler).toHaveBeenCalledOnce();
|
|
378
378
|
expect(item.ack).toHaveBeenCalledTimes(2);
|
|
379
379
|
});
|
|
380
|
-
it(
|
|
380
|
+
it("does not acknowledge interactive envelopes a second time on the generic event stream", async ()=>{
|
|
381
381
|
const socket = new FakeSocketClient();
|
|
382
382
|
const adapter = new SlackAdapter(config, {
|
|
383
383
|
socketClient: socket,
|
|
@@ -389,15 +389,15 @@ describe('SlackAdapter', ()=>{
|
|
|
389
389
|
});
|
|
390
390
|
await adapter.start();
|
|
391
391
|
const ack = vi.fn().mockResolvedValue(undefined);
|
|
392
|
-
socket.emit(
|
|
393
|
-
type:
|
|
392
|
+
socket.emit("slack_event", {
|
|
393
|
+
type: "interactive",
|
|
394
394
|
ack,
|
|
395
395
|
body: {}
|
|
396
396
|
});
|
|
397
397
|
await new Promise((resolve)=>setTimeout(resolve, 0));
|
|
398
398
|
expect(ack).not.toHaveBeenCalled();
|
|
399
399
|
});
|
|
400
|
-
it(
|
|
400
|
+
it("acknowledges malformed and wrong-workspace block actions without delivery", async ()=>{
|
|
401
401
|
const socket = new FakeSocketClient();
|
|
402
402
|
const handler = vi.fn();
|
|
403
403
|
const adapter = new SlackAdapter(config, {
|
|
@@ -411,12 +411,12 @@ describe('SlackAdapter', ()=>{
|
|
|
411
411
|
adapter.onInteraction(handler);
|
|
412
412
|
await adapter.start();
|
|
413
413
|
const ack = vi.fn().mockResolvedValue(undefined);
|
|
414
|
-
socket.emit(
|
|
414
|
+
socket.emit("interactive", {
|
|
415
415
|
ack,
|
|
416
416
|
body: {
|
|
417
|
-
type:
|
|
417
|
+
type: "block_actions",
|
|
418
418
|
team: {
|
|
419
|
-
id:
|
|
419
|
+
id: "T999"
|
|
420
420
|
},
|
|
421
421
|
actions: []
|
|
422
422
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/__tests__/adapters/slack/SlackAdapter.test.ts"],"sourcesContent":["import { EventEmitter } from 'node:events';\nimport { SlackAdapter, validateSlackAppToken, validateSlackCredentials } from '../../../adapters/slack/SlackAdapter.js';\nimport type { SlackConfig } from '../../../types.js';\n\nclass FakeSocketClient extends EventEmitter {\n start = vi.fn().mockResolvedValue(undefined);\n disconnect = vi.fn().mockResolvedValue(undefined);\n}\n\nconst config: SlackConfig = {\n appToken: 'xapp-fake',\n botToken: 'xoxb-fake',\n appId: 'A123',\n botUserId: 'U-BOT',\n workspaceId: 'T123',\n transport: 'socket-mode',\n audience: 'dm',\n};\n\nfunction envelope(overrides: Record<string, unknown> = {}) {\n return {\n type: 'events_api',\n ack: vi.fn().mockResolvedValue(undefined),\n body: {\n team_id: 'T123',\n event_id: 'Ev123',\n event: {\n type: 'message',\n channel_type: 'im',\n channel: 'D123',\n user: 'U123',\n text: 'run tests',\n ts: '100.1',\n event_ts: '100.1',\n ...overrides,\n },\n },\n };\n}\n\ndescribe('SlackAdapter', () => {\n it('validates bot identity without returning either credential', async () => {\n const authTest = vi.fn().mockResolvedValue({ ok: true, app_id: 'A123', user_id: 'U-BOT', team_id: 'T123', team: 'Sandbox' });\n await expect(validateSlackCredentials('xoxb-fake', { authTest })).resolves.toEqual({\n appId: 'A123', botUserId: 'U-BOT', workspaceId: 'T123', workspaceName: 'Sandbox',\n });\n });\n\n it('accepts Slack documented bot identity without an app ID', async () => {\n const authTest = vi.fn().mockResolvedValue({\n ok: true,\n bot_id: 'B123',\n user_id: 'U-BOT',\n team_id: 'T123',\n team: 'Sandbox',\n });\n await expect(validateSlackCredentials('xoxb-fake', { authTest })).resolves.toEqual({\n botUserId: 'U-BOT',\n workspaceId: 'T123',\n workspaceName: 'Sandbox',\n });\n });\n\n it('rejects incomplete Slack credential identity', async () => {\n const authTest = vi.fn().mockResolvedValue({ ok: true, team_id: 'T123' });\n await expect(validateSlackCredentials('xoxb-fake', { authTest })).rejects.toThrow('Slack bot token returned incomplete identity');\n });\n\n it('validates an app token through apps.connections.open', async () => {\n const open = vi.fn().mockResolvedValue({ ok: true, url: 'wss://wss-primary.slack.com/link/' });\n await expect(validateSlackAppToken('xapp-fake', { open })).resolves.toBeUndefined();\n expect(open).toHaveBeenCalledOnce();\n });\n\n it('rejects an app token that cannot open Socket Mode', async () => {\n const open = vi.fn().mockResolvedValue({ ok: false });\n await expect(validateSlackAppToken('xapp-fake', { open })).rejects.toThrow('Slack app token cannot open Socket Mode');\n });\n\n it('starts and stops the official Socket Mode client and reports health', async () => {\n const socket = new FakeSocketClient();\n const adapter = new SlackAdapter(config, { socketClient: socket, webClient: { chat: { postMessage: vi.fn() } } });\n expect(await adapter.isHealthy()).toBe(false);\n await adapter.start();\n expect(socket.start).toHaveBeenCalledOnce();\n expect(await adapter.isHealthy()).toBe(true);\n await adapter.stop();\n expect(socket.disconnect).toHaveBeenCalledOnce();\n expect(await adapter.isHealthy()).toBe(false);\n });\n\n it('does not register duplicate listeners across restarts', async () => {\n const socket = new FakeSocketClient();\n const adapter = new SlackAdapter(config, { socketClient: socket, webClient: { chat: { postMessage: vi.fn() } } });\n await adapter.start();\n await adapter.stop();\n await adapter.start();\n expect(socket.listenerCount('slack_event')).toBe(1);\n expect(socket.listenerCount('interactive')).toBe(1);\n socket.emit('disconnected');\n expect(await adapter.isHealthy()).toBe(false);\n socket.emit('connected');\n expect(await adapter.isHealthy()).toBe(true);\n });\n\n it('acknowledges before delivering one normalized workspace DM event', async () => {\n const socket = new FakeSocketClient();\n const adapter = new SlackAdapter(config, { socketClient: socket, webClient: { chat: { postMessage: vi.fn() } } });\n const order: string[] = [];\n adapter.onMessage(vi.fn(async (message) => {\n order.push('handler');\n expect(message).toMatchObject({\n channelType: 'slack', chatId: 'D123', userId: 'U123', workspaceId: 'T123', messageId: 'Ev123', text: 'run tests',\n });\n }));\n await adapter.start();\n const item = envelope();\n item.ack.mockImplementation(async () => { order.push('ack'); });\n socket.emit('slack_event', item);\n await vi.waitFor(() => expect(order).toEqual(['ack', 'handler']));\n socket.emit('slack_event', item);\n await new Promise((resolve) => setTimeout(resolve, 0));\n expect(order).toEqual(['ack', 'handler', 'ack']);\n });\n\n it.each([\n ['wrong workspace', {}, { team_id: 'T999' }],\n ['bot message', { bot_id: 'B123' }, {}],\n ['self message', { user: 'U-BOT' }, {}],\n ['subtype', { subtype: 'message_changed' }, {}],\n ['non-DM', { channel_type: 'channel' }, {}],\n ])('ignores %s events', async (_name, eventOverrides, bodyOverrides) => {\n const socket = new FakeSocketClient();\n const handler = vi.fn();\n const adapter = new SlackAdapter(config, { socketClient: socket, webClient: { chat: { postMessage: vi.fn() } } });\n adapter.onMessage(handler);\n await adapter.start();\n const item = envelope(eventOverrides);\n Object.assign(item.body, bodyOverrides);\n socket.emit('slack_event', item);\n await vi.waitFor(() => expect(item.ack).toHaveBeenCalled());\n expect(handler).not.toHaveBeenCalled();\n });\n\n it('delivers an ordinary workspace DM immediately without pairing', async () => {\n const socket = new FakeSocketClient();\n const handler = vi.fn();\n const adapter = new SlackAdapter(config, {\n socketClient: socket,\n webClient: { chat: { postMessage: vi.fn() } },\n });\n adapter.onMessage(handler);\n await adapter.start();\n\n socket.emit('slack_event', envelope({ text: 'run tests' }));\n\n await vi.waitFor(() => expect(handler).toHaveBeenCalledWith(expect.objectContaining({\n chatId: 'D123', userId: 'U123', text: 'run tests', workspaceId: 'T123',\n })));\n });\n\n it('sends an accessible Block Kit question and finalizes its actions', async () => {\n const socket = new FakeSocketClient();\n const postMessage = vi.fn().mockResolvedValue({ ok: true, ts: '400.1' });\n const update = vi.fn().mockResolvedValue({ ok: true });\n const adapter = new SlackAdapter(config, { socketClient: socket, webClient: { chat: { postMessage, update } } });\n await expect(adapter.sendQuestion('D123', {\n id: 'q1', header: 'Scope', question: 'Choose one', allowSkip: true,\n options: [{ label: 'Safe', description: 'Read only', value: '1' }],\n })).resolves.toEqual({ messageId: '400.1' });\n expect(postMessage).toHaveBeenCalledWith(expect.objectContaining({\n channel: 'D123', text: 'Scope: Choose one', blocks: expect.any(Array),\n }));\n await adapter.finalizeInteraction('D123', '400.1');\n expect(update).toHaveBeenCalledWith({ channel: 'D123', ts: '400.1', blocks: [] });\n });\n\n it('escapes Slack control syntax in question fallback text', async () => {\n const postMessage = vi.fn().mockResolvedValue({ ok: true, ts: '400.1' });\n const adapter = new SlackAdapter(config, {\n socketClient: new FakeSocketClient(),\n webClient: { chat: { postMessage } },\n });\n await adapter.sendQuestion('D123', {\n id: 'q1', header: '<!channel>', question: 'Choose <one> & continue', allowSkip: false,\n options: [{ label: 'Safe', value: '1' }],\n });\n expect(postMessage).toHaveBeenCalledWith(expect.objectContaining({\n text: '<!channel>: Choose <one> & continue',\n }));\n });\n\n it('acknowledges and delivers one valid workspace block action', async () => {\n const socket = new FakeSocketClient();\n const handler = vi.fn();\n const adapter = new SlackAdapter(config, { socketClient: socket, webClient: { chat: { postMessage: vi.fn() } } });\n adapter.onInteraction(handler);\n await adapter.start();\n const item = {\n ack: vi.fn().mockResolvedValue(undefined),\n body: {\n type: 'block_actions', team: { id: 'T123' }, user: { id: 'U123' },\n channel: { id: 'D123' }, container: { message_ts: '400.1' },\n actions: [{ action_id: 'ai_devkit_question', action_ts: '401.1', value: 'q1:1' }],\n },\n };\n socket.emit('interactive', item);\n await vi.waitFor(() => expect(handler).toHaveBeenCalledOnce());\n expect(item.ack).toHaveBeenCalledOnce();\n expect(handler).toHaveBeenCalledWith(expect.objectContaining({\n workspaceId: 'T123', chatId: 'D123', userId: 'U123', messageId: '400.1', value: 'q1:1',\n }));\n socket.emit('interactive', item);\n await new Promise((resolve) => setTimeout(resolve, 0));\n expect(handler).toHaveBeenCalledOnce();\n expect(item.ack).toHaveBeenCalledTimes(2);\n });\n\n it('does not acknowledge interactive envelopes a second time on the generic event stream', async () => {\n const socket = new FakeSocketClient();\n const adapter = new SlackAdapter(config, { socketClient: socket, webClient: { chat: { postMessage: vi.fn() } } });\n await adapter.start();\n const ack = vi.fn().mockResolvedValue(undefined);\n socket.emit('slack_event', { type: 'interactive', ack, body: {} });\n await new Promise((resolve) => setTimeout(resolve, 0));\n expect(ack).not.toHaveBeenCalled();\n });\n\n it('acknowledges malformed and wrong-workspace block actions without delivery', async () => {\n const socket = new FakeSocketClient();\n const handler = vi.fn();\n const adapter = new SlackAdapter(config, { socketClient: socket, webClient: { chat: { postMessage: vi.fn() } } });\n adapter.onInteraction(handler);\n await adapter.start();\n const ack = vi.fn().mockResolvedValue(undefined);\n socket.emit('interactive', { ack, body: { type: 'block_actions', team: { id: 'T999' }, actions: [] } });\n await vi.waitFor(() => expect(ack).toHaveBeenCalled());\n expect(handler).not.toHaveBeenCalled();\n });\n});\n"],"names":["EventEmitter","SlackAdapter","validateSlackAppToken","validateSlackCredentials","FakeSocketClient","start","vi","fn","mockResolvedValue","undefined","disconnect","config","appToken","botToken","appId","botUserId","workspaceId","transport","audience","envelope","overrides","type","ack","body","team_id","event_id","event","channel_type","channel","user","text","ts","event_ts","describe","it","authTest","ok","app_id","user_id","team","expect","resolves","toEqual","workspaceName","bot_id","rejects","toThrow","open","url","toBeUndefined","toHaveBeenCalledOnce","socket","adapter","socketClient","webClient","chat","postMessage","isHealthy","toBe","stop","listenerCount","emit","order","onMessage","message","push","toMatchObject","channelType","chatId","userId","messageId","item","mockImplementation","waitFor","Promise","resolve","setTimeout","each","subtype","_name","eventOverrides","bodyOverrides","handler","Object","assign","toHaveBeenCalled","not","toHaveBeenCalledWith","objectContaining","update","sendQuestion","id","header","question","allowSkip","options","label","description","value","blocks","any","Array","finalizeInteraction","onInteraction","container","message_ts","actions","action_id","action_ts","toHaveBeenCalledTimes"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../src/__tests__/adapters/slack/SlackAdapter.test.ts"],"sourcesContent":["import { EventEmitter } from \"node:events\";\nimport {\n SlackAdapter,\n validateSlackAppToken,\n validateSlackCredentials,\n} from \"../../../adapters/slack/SlackAdapter.js\";\nimport type { SlackConfig } from \"../../../types.js\";\n\nclass FakeSocketClient extends EventEmitter {\n start = vi.fn().mockResolvedValue(undefined);\n disconnect = vi.fn().mockResolvedValue(undefined);\n}\n\nconst config: SlackConfig = {\n appToken: \"xapp-fake\",\n botToken: \"xoxb-fake\",\n appId: \"A123\",\n botUserId: \"U-BOT\",\n workspaceId: \"T123\",\n transport: \"socket-mode\",\n audience: \"dm\",\n};\n\nfunction envelope(overrides: Record<string, unknown> = {}) {\n return {\n type: \"events_api\",\n ack: vi.fn().mockResolvedValue(undefined),\n body: {\n team_id: \"T123\",\n event_id: \"Ev123\",\n event: {\n type: \"message\",\n channel_type: \"im\",\n channel: \"D123\",\n user: \"U123\",\n text: \"run tests\",\n ts: \"100.1\",\n event_ts: \"100.1\",\n ...overrides,\n },\n },\n };\n}\n\ndescribe(\"SlackAdapter\", () => {\n it(\"validates bot identity without returning either credential\", async () => {\n const authTest = vi.fn().mockResolvedValue({\n ok: true,\n app_id: \"A123\",\n user_id: \"U-BOT\",\n team_id: \"T123\",\n team: \"Sandbox\",\n });\n await expect(validateSlackCredentials(\"xoxb-fake\", { authTest })).resolves.toEqual({\n appId: \"A123\",\n botUserId: \"U-BOT\",\n workspaceId: \"T123\",\n workspaceName: \"Sandbox\",\n });\n });\n\n it(\"accepts Slack documented bot identity without an app ID\", async () => {\n const authTest = vi.fn().mockResolvedValue({\n ok: true,\n bot_id: \"B123\",\n user_id: \"U-BOT\",\n team_id: \"T123\",\n team: \"Sandbox\",\n });\n await expect(validateSlackCredentials(\"xoxb-fake\", { authTest })).resolves.toEqual({\n botUserId: \"U-BOT\",\n workspaceId: \"T123\",\n workspaceName: \"Sandbox\",\n });\n });\n\n it(\"rejects incomplete Slack credential identity\", async () => {\n const authTest = vi.fn().mockResolvedValue({ ok: true, team_id: \"T123\" });\n await expect(validateSlackCredentials(\"xoxb-fake\", { authTest })).rejects.toThrow(\n \"Slack bot token returned incomplete identity\",\n );\n });\n\n it(\"validates an app token through apps.connections.open\", async () => {\n const open = vi.fn().mockResolvedValue({ ok: true, url: \"wss://wss-primary.slack.com/link/\" });\n await expect(validateSlackAppToken(\"xapp-fake\", { open })).resolves.toBeUndefined();\n expect(open).toHaveBeenCalledOnce();\n });\n\n it(\"rejects an app token that cannot open Socket Mode\", async () => {\n const open = vi.fn().mockResolvedValue({ ok: false });\n await expect(validateSlackAppToken(\"xapp-fake\", { open })).rejects.toThrow(\n \"Slack app token cannot open Socket Mode\",\n );\n });\n\n it(\"starts and stops the official Socket Mode client and reports health\", async () => {\n const socket = new FakeSocketClient();\n const adapter = new SlackAdapter(config, {\n socketClient: socket,\n webClient: { chat: { postMessage: vi.fn() } },\n });\n expect(await adapter.isHealthy()).toBe(false);\n await adapter.start();\n expect(socket.start).toHaveBeenCalledOnce();\n expect(await adapter.isHealthy()).toBe(true);\n await adapter.stop();\n expect(socket.disconnect).toHaveBeenCalledOnce();\n expect(await adapter.isHealthy()).toBe(false);\n });\n\n it(\"does not register duplicate listeners across restarts\", async () => {\n const socket = new FakeSocketClient();\n const adapter = new SlackAdapter(config, {\n socketClient: socket,\n webClient: { chat: { postMessage: vi.fn() } },\n });\n await adapter.start();\n await adapter.stop();\n await adapter.start();\n expect(socket.listenerCount(\"slack_event\")).toBe(1);\n expect(socket.listenerCount(\"interactive\")).toBe(1);\n socket.emit(\"disconnected\");\n expect(await adapter.isHealthy()).toBe(false);\n socket.emit(\"connected\");\n expect(await adapter.isHealthy()).toBe(true);\n });\n\n it(\"acknowledges before delivering one normalized workspace DM event\", async () => {\n const socket = new FakeSocketClient();\n const adapter = new SlackAdapter(config, {\n socketClient: socket,\n webClient: { chat: { postMessage: vi.fn() } },\n });\n const order: string[] = [];\n adapter.onMessage(\n vi.fn(async (message) => {\n order.push(\"handler\");\n expect(message).toMatchObject({\n channelType: \"slack\",\n chatId: \"D123\",\n userId: \"U123\",\n workspaceId: \"T123\",\n messageId: \"Ev123\",\n text: \"run tests\",\n });\n }),\n );\n await adapter.start();\n const item = envelope();\n item.ack.mockImplementation(async () => {\n order.push(\"ack\");\n });\n socket.emit(\"slack_event\", item);\n await vi.waitFor(() => expect(order).toEqual([\"ack\", \"handler\"]));\n socket.emit(\"slack_event\", item);\n await new Promise((resolve) => setTimeout(resolve, 0));\n expect(order).toEqual([\"ack\", \"handler\", \"ack\"]);\n });\n\n it.each([\n [\"wrong workspace\", {}, { team_id: \"T999\" }],\n [\"bot message\", { bot_id: \"B123\" }, {}],\n [\"self message\", { user: \"U-BOT\" }, {}],\n [\"subtype\", { subtype: \"message_changed\" }, {}],\n [\"non-DM\", { channel_type: \"channel\" }, {}],\n ])(\"ignores %s events\", async (_name, eventOverrides, bodyOverrides) => {\n const socket = new FakeSocketClient();\n const handler = vi.fn();\n const adapter = new SlackAdapter(config, {\n socketClient: socket,\n webClient: { chat: { postMessage: vi.fn() } },\n });\n adapter.onMessage(handler);\n await adapter.start();\n const item = envelope(eventOverrides);\n Object.assign(item.body, bodyOverrides);\n socket.emit(\"slack_event\", item);\n await vi.waitFor(() => expect(item.ack).toHaveBeenCalled());\n expect(handler).not.toHaveBeenCalled();\n });\n\n it(\"delivers an ordinary workspace DM immediately without pairing\", async () => {\n const socket = new FakeSocketClient();\n const handler = vi.fn();\n const adapter = new SlackAdapter(config, {\n socketClient: socket,\n webClient: { chat: { postMessage: vi.fn() } },\n });\n adapter.onMessage(handler);\n await adapter.start();\n\n socket.emit(\"slack_event\", envelope({ text: \"run tests\" }));\n\n await vi.waitFor(() =>\n expect(handler).toHaveBeenCalledWith(\n expect.objectContaining({\n chatId: \"D123\",\n userId: \"U123\",\n text: \"run tests\",\n workspaceId: \"T123\",\n }),\n ),\n );\n });\n\n it(\"sends an accessible Block Kit question and finalizes its actions\", async () => {\n const socket = new FakeSocketClient();\n const postMessage = vi.fn().mockResolvedValue({ ok: true, ts: \"400.1\" });\n const update = vi.fn().mockResolvedValue({ ok: true });\n const adapter = new SlackAdapter(config, {\n socketClient: socket,\n webClient: { chat: { postMessage, update } },\n });\n await expect(\n adapter.sendQuestion(\"D123\", {\n id: \"q1\",\n header: \"Scope\",\n question: \"Choose one\",\n allowSkip: true,\n options: [{ label: \"Safe\", description: \"Read only\", value: \"1\" }],\n }),\n ).resolves.toEqual({ messageId: \"400.1\" });\n expect(postMessage).toHaveBeenCalledWith(\n expect.objectContaining({\n channel: \"D123\",\n text: \"Scope: Choose one\",\n blocks: expect.any(Array),\n }),\n );\n await adapter.finalizeInteraction(\"D123\", \"400.1\");\n expect(update).toHaveBeenCalledWith({ channel: \"D123\", ts: \"400.1\", blocks: [] });\n });\n\n it(\"escapes Slack control syntax in question fallback text\", async () => {\n const postMessage = vi.fn().mockResolvedValue({ ok: true, ts: \"400.1\" });\n const adapter = new SlackAdapter(config, {\n socketClient: new FakeSocketClient(),\n webClient: { chat: { postMessage } },\n });\n await adapter.sendQuestion(\"D123\", {\n id: \"q1\",\n header: \"<!channel>\",\n question: \"Choose <one> & continue\",\n allowSkip: false,\n options: [{ label: \"Safe\", value: \"1\" }],\n });\n expect(postMessage).toHaveBeenCalledWith(\n expect.objectContaining({\n text: \"<!channel>: Choose <one> & continue\",\n }),\n );\n });\n\n it(\"acknowledges and delivers one valid workspace block action\", async () => {\n const socket = new FakeSocketClient();\n const handler = vi.fn();\n const adapter = new SlackAdapter(config, {\n socketClient: socket,\n webClient: { chat: { postMessage: vi.fn() } },\n });\n adapter.onInteraction(handler);\n await adapter.start();\n const item = {\n ack: vi.fn().mockResolvedValue(undefined),\n body: {\n type: \"block_actions\",\n team: { id: \"T123\" },\n user: { id: \"U123\" },\n channel: { id: \"D123\" },\n container: { message_ts: \"400.1\" },\n actions: [{ action_id: \"ai_devkit_question\", action_ts: \"401.1\", value: \"q1:1\" }],\n },\n };\n socket.emit(\"interactive\", item);\n await vi.waitFor(() => expect(handler).toHaveBeenCalledOnce());\n expect(item.ack).toHaveBeenCalledOnce();\n expect(handler).toHaveBeenCalledWith(\n expect.objectContaining({\n workspaceId: \"T123\",\n chatId: \"D123\",\n userId: \"U123\",\n messageId: \"400.1\",\n value: \"q1:1\",\n }),\n );\n socket.emit(\"interactive\", item);\n await new Promise((resolve) => setTimeout(resolve, 0));\n expect(handler).toHaveBeenCalledOnce();\n expect(item.ack).toHaveBeenCalledTimes(2);\n });\n\n it(\"does not acknowledge interactive envelopes a second time on the generic event stream\", async () => {\n const socket = new FakeSocketClient();\n const adapter = new SlackAdapter(config, {\n socketClient: socket,\n webClient: { chat: { postMessage: vi.fn() } },\n });\n await adapter.start();\n const ack = vi.fn().mockResolvedValue(undefined);\n socket.emit(\"slack_event\", { type: \"interactive\", ack, body: {} });\n await new Promise((resolve) => setTimeout(resolve, 0));\n expect(ack).not.toHaveBeenCalled();\n });\n\n it(\"acknowledges malformed and wrong-workspace block actions without delivery\", async () => {\n const socket = new FakeSocketClient();\n const handler = vi.fn();\n const adapter = new SlackAdapter(config, {\n socketClient: socket,\n webClient: { chat: { postMessage: vi.fn() } },\n });\n adapter.onInteraction(handler);\n await adapter.start();\n const ack = vi.fn().mockResolvedValue(undefined);\n socket.emit(\"interactive\", {\n ack,\n body: { type: \"block_actions\", team: { id: \"T999\" }, actions: [] },\n });\n await vi.waitFor(() => expect(ack).toHaveBeenCalled());\n expect(handler).not.toHaveBeenCalled();\n });\n});\n"],"names":["EventEmitter","SlackAdapter","validateSlackAppToken","validateSlackCredentials","FakeSocketClient","start","vi","fn","mockResolvedValue","undefined","disconnect","config","appToken","botToken","appId","botUserId","workspaceId","transport","audience","envelope","overrides","type","ack","body","team_id","event_id","event","channel_type","channel","user","text","ts","event_ts","describe","it","authTest","ok","app_id","user_id","team","expect","resolves","toEqual","workspaceName","bot_id","rejects","toThrow","open","url","toBeUndefined","toHaveBeenCalledOnce","socket","adapter","socketClient","webClient","chat","postMessage","isHealthy","toBe","stop","listenerCount","emit","order","onMessage","message","push","toMatchObject","channelType","chatId","userId","messageId","item","mockImplementation","waitFor","Promise","resolve","setTimeout","each","subtype","_name","eventOverrides","bodyOverrides","handler","Object","assign","toHaveBeenCalled","not","toHaveBeenCalledWith","objectContaining","update","sendQuestion","id","header","question","allowSkip","options","label","description","value","blocks","any","Array","finalizeInteraction","onInteraction","container","message_ts","actions","action_id","action_ts","toHaveBeenCalledTimes"],"mappings":"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"}
|