@urugus/slack-cli 0.1.1 → 0.1.4
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/.claude/settings.local.json +4 -1
- package/README.md +1 -1
- package/dist/utils/slack-api-client.d.ts.map +1 -1
- package/dist/utils/slack-api-client.js +70 -10
- package/dist/utils/slack-api-client.js.map +1 -1
- package/package.json +2 -2
- package/src/utils/slack-api-client.ts +77 -14
- package/tests/commands/history.test.ts +38 -0
- package/tests/utils/slack-api-client.test.ts +99 -0
package/README.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack-api-client.d.ts","sourceRoot":"","sources":["../../src/utils/slack-api-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAEpE,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,OAAO,CAAC,EAAE;QACR,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,OAAO,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAY;gBAEd,KAAK,EAAE,MAAM;IAInB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAO5E,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"slack-api-client.d.ts","sourceRoot":"","sources":["../../src/utils/slack-api-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,12 +13,22 @@ class SlackApiClient {
|
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
15
|
async listChannels(options) {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
const channels = [];
|
|
17
|
+
let cursor;
|
|
18
|
+
// Paginate through all channels
|
|
19
|
+
do {
|
|
20
|
+
const response = await this.client.conversations.list({
|
|
21
|
+
types: options.types,
|
|
22
|
+
exclude_archived: options.exclude_archived,
|
|
23
|
+
limit: options.limit,
|
|
24
|
+
cursor,
|
|
25
|
+
});
|
|
26
|
+
if (response.channels) {
|
|
27
|
+
channels.push(...response.channels);
|
|
28
|
+
}
|
|
29
|
+
cursor = response.response_metadata?.next_cursor;
|
|
30
|
+
} while (cursor);
|
|
31
|
+
return channels;
|
|
22
32
|
}
|
|
23
33
|
async getHistory(channel, options) {
|
|
24
34
|
// First, resolve channel name to ID if needed
|
|
@@ -30,9 +40,34 @@ class SlackApiClient {
|
|
|
30
40
|
exclude_archived: true,
|
|
31
41
|
limit: 1000,
|
|
32
42
|
});
|
|
33
|
-
|
|
43
|
+
// Try multiple matching strategies
|
|
44
|
+
const foundChannel = channels.find((c) => {
|
|
45
|
+
// Direct name match
|
|
46
|
+
if (c.name === channel)
|
|
47
|
+
return true;
|
|
48
|
+
// Match without # prefix
|
|
49
|
+
if (c.name === channel.replace('#', ''))
|
|
50
|
+
return true;
|
|
51
|
+
// Case-insensitive match
|
|
52
|
+
if (c.name?.toLowerCase() === channel.toLowerCase())
|
|
53
|
+
return true;
|
|
54
|
+
// Match with normalized name
|
|
55
|
+
if (c.name_normalized === channel)
|
|
56
|
+
return true;
|
|
57
|
+
return false;
|
|
58
|
+
});
|
|
34
59
|
if (!foundChannel) {
|
|
35
|
-
|
|
60
|
+
// Provide helpful error message
|
|
61
|
+
const similarChannels = channels
|
|
62
|
+
.filter((c) => c.name?.toLowerCase().includes(channel.toLowerCase()))
|
|
63
|
+
.slice(0, 5)
|
|
64
|
+
.map((c) => c.name);
|
|
65
|
+
if (similarChannels.length > 0) {
|
|
66
|
+
throw new Error(`Channel '${channel}' not found. Did you mean one of these? ${similarChannels.join(', ')}`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
throw new Error(`Channel '${channel}' not found. Make sure you are a member of this channel.`);
|
|
70
|
+
}
|
|
36
71
|
}
|
|
37
72
|
channelId = foundChannel.id;
|
|
38
73
|
}
|
|
@@ -104,9 +139,34 @@ class SlackApiClient {
|
|
|
104
139
|
exclude_archived: true,
|
|
105
140
|
limit: 1000,
|
|
106
141
|
});
|
|
107
|
-
|
|
142
|
+
// Try multiple matching strategies (same as getHistory)
|
|
143
|
+
const channel = channels.find((c) => {
|
|
144
|
+
// Direct name match
|
|
145
|
+
if (c.name === channelNameOrId)
|
|
146
|
+
return true;
|
|
147
|
+
// Match without # prefix
|
|
148
|
+
if (c.name === channelNameOrId.replace('#', ''))
|
|
149
|
+
return true;
|
|
150
|
+
// Case-insensitive match
|
|
151
|
+
if (c.name?.toLowerCase() === channelNameOrId.toLowerCase())
|
|
152
|
+
return true;
|
|
153
|
+
// Match with normalized name
|
|
154
|
+
if (c.name_normalized === channelNameOrId)
|
|
155
|
+
return true;
|
|
156
|
+
return false;
|
|
157
|
+
});
|
|
108
158
|
if (!channel) {
|
|
109
|
-
|
|
159
|
+
// Provide helpful error message
|
|
160
|
+
const similarChannels = channels
|
|
161
|
+
.filter((c) => c.name?.toLowerCase().includes(channelNameOrId.toLowerCase()))
|
|
162
|
+
.slice(0, 5)
|
|
163
|
+
.map((c) => c.name);
|
|
164
|
+
if (similarChannels.length > 0) {
|
|
165
|
+
throw new Error(`Channel '${channelNameOrId}' not found. Did you mean one of these? ${similarChannels.join(', ')}`);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
throw new Error(`Channel '${channelNameOrId}' not found. Make sure you are a member of this channel.`);
|
|
169
|
+
}
|
|
110
170
|
}
|
|
111
171
|
channelId = channel.id;
|
|
112
172
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack-api-client.js","sourceRoot":"","sources":["../../src/utils/slack-api-client.ts"],"names":[],"mappings":";;;AAAA,4CAAoE;AAoEpE,MAAa,cAAc;IAGzB,YAAY,KAAa;QACvB,IAAI,CAAC,MAAM,GAAG,IAAI,mBAAS,CAAC,KAAK,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,OAAe,EAAE,IAAY;QAC7C,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC;YACxC,OAAO;YACP,IAAI;SACL,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,OAA4B;QAC7C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"slack-api-client.js","sourceRoot":"","sources":["../../src/utils/slack-api-client.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@urugus/slack-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "A command-line tool for sending messages to Slack",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -53,4 +53,4 @@
|
|
|
53
53
|
"engines": {
|
|
54
54
|
"node": ">=16.0.0"
|
|
55
55
|
}
|
|
56
|
-
}
|
|
56
|
+
}
|
|
@@ -81,13 +81,26 @@ export class SlackApiClient {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
async listChannels(options: ListChannelsOptions): Promise<Channel[]> {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
const channels: Channel[] = [];
|
|
85
|
+
let cursor: string | undefined;
|
|
86
|
+
|
|
87
|
+
// Paginate through all channels
|
|
88
|
+
do {
|
|
89
|
+
const response = await this.client.conversations.list({
|
|
90
|
+
types: options.types,
|
|
91
|
+
exclude_archived: options.exclude_archived,
|
|
92
|
+
limit: options.limit,
|
|
93
|
+
cursor,
|
|
94
|
+
});
|
|
89
95
|
|
|
90
|
-
|
|
96
|
+
if (response.channels) {
|
|
97
|
+
channels.push(...(response.channels as Channel[]));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
cursor = response.response_metadata?.next_cursor;
|
|
101
|
+
} while (cursor);
|
|
102
|
+
|
|
103
|
+
return channels;
|
|
91
104
|
}
|
|
92
105
|
|
|
93
106
|
async getHistory(channel: string, options: HistoryOptions): Promise<HistoryResult> {
|
|
@@ -100,11 +113,36 @@ export class SlackApiClient {
|
|
|
100
113
|
exclude_archived: true,
|
|
101
114
|
limit: 1000,
|
|
102
115
|
});
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
)
|
|
116
|
+
|
|
117
|
+
// Try multiple matching strategies
|
|
118
|
+
const foundChannel = channels.find((c) => {
|
|
119
|
+
// Direct name match
|
|
120
|
+
if (c.name === channel) return true;
|
|
121
|
+
// Match without # prefix
|
|
122
|
+
if (c.name === channel.replace('#', '')) return true;
|
|
123
|
+
// Case-insensitive match
|
|
124
|
+
if (c.name?.toLowerCase() === channel.toLowerCase()) return true;
|
|
125
|
+
// Match with normalized name
|
|
126
|
+
if (c.name_normalized === channel) return true;
|
|
127
|
+
return false;
|
|
128
|
+
});
|
|
129
|
+
|
|
106
130
|
if (!foundChannel) {
|
|
107
|
-
|
|
131
|
+
// Provide helpful error message
|
|
132
|
+
const similarChannels = channels
|
|
133
|
+
.filter((c) => c.name?.toLowerCase().includes(channel.toLowerCase()))
|
|
134
|
+
.slice(0, 5)
|
|
135
|
+
.map((c) => c.name);
|
|
136
|
+
|
|
137
|
+
if (similarChannels.length > 0) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Channel '${channel}' not found. Did you mean one of these? ${similarChannels.join(', ')}`
|
|
140
|
+
);
|
|
141
|
+
} else {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`Channel '${channel}' not found. Make sure you are a member of this channel.`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
108
146
|
}
|
|
109
147
|
channelId = foundChannel.id;
|
|
110
148
|
}
|
|
@@ -188,11 +226,36 @@ export class SlackApiClient {
|
|
|
188
226
|
exclude_archived: true,
|
|
189
227
|
limit: 1000,
|
|
190
228
|
});
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
)
|
|
229
|
+
|
|
230
|
+
// Try multiple matching strategies (same as getHistory)
|
|
231
|
+
const channel = channels.find((c) => {
|
|
232
|
+
// Direct name match
|
|
233
|
+
if (c.name === channelNameOrId) return true;
|
|
234
|
+
// Match without # prefix
|
|
235
|
+
if (c.name === channelNameOrId.replace('#', '')) return true;
|
|
236
|
+
// Case-insensitive match
|
|
237
|
+
if (c.name?.toLowerCase() === channelNameOrId.toLowerCase()) return true;
|
|
238
|
+
// Match with normalized name
|
|
239
|
+
if (c.name_normalized === channelNameOrId) return true;
|
|
240
|
+
return false;
|
|
241
|
+
});
|
|
242
|
+
|
|
194
243
|
if (!channel) {
|
|
195
|
-
|
|
244
|
+
// Provide helpful error message
|
|
245
|
+
const similarChannels = channels
|
|
246
|
+
.filter((c) => c.name?.toLowerCase().includes(channelNameOrId.toLowerCase()))
|
|
247
|
+
.slice(0, 5)
|
|
248
|
+
.map((c) => c.name);
|
|
249
|
+
|
|
250
|
+
if (similarChannels.length > 0) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
`Channel '${channelNameOrId}' not found. Did you mean one of these? ${similarChannels.join(', ')}`
|
|
253
|
+
);
|
|
254
|
+
} else {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`Channel '${channelNameOrId}' not found. Make sure you are a member of this channel.`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
196
259
|
}
|
|
197
260
|
channelId = channel.id;
|
|
198
261
|
}
|
|
@@ -176,6 +176,44 @@ describe('history command', () => {
|
|
|
176
176
|
expect(mockConsole.errorSpy).toHaveBeenCalledWith(expect.stringContaining('Error:'), expect.any(String));
|
|
177
177
|
expect(mockConsole.exitSpy).toHaveBeenCalledWith(1);
|
|
178
178
|
});
|
|
179
|
+
|
|
180
|
+
it('should show helpful error when channel not found', async () => {
|
|
181
|
+
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
182
|
+
token: 'test-token',
|
|
183
|
+
updatedAt: new Date().toISOString()
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Mock getHistory to throw our new helpful error
|
|
187
|
+
vi.mocked(mockSlackClient.getHistory).mockRejectedValue(
|
|
188
|
+
new Error("Channel 'times_sakashi' not found. Make sure you are a member of this channel.")
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
await program.parseAsync(['node', 'slack-cli', 'history', '-c', 'times_sakashi']);
|
|
192
|
+
|
|
193
|
+
expect(mockSlackClient.getHistory).toHaveBeenCalledWith('times_sakashi', { limit: 10 });
|
|
194
|
+
expect(mockConsole.errorSpy).toHaveBeenCalledWith(
|
|
195
|
+
expect.stringContaining('Error:'),
|
|
196
|
+
expect.stringContaining('Make sure you are a member of this channel')
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should find private channels with underscore in name', async () => {
|
|
201
|
+
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
202
|
+
token: 'test-token',
|
|
203
|
+
updatedAt: new Date().toISOString()
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Mock the getHistory method to simulate successful channel lookup
|
|
207
|
+
vi.mocked(mockSlackClient.getHistory).mockResolvedValue({
|
|
208
|
+
messages: [],
|
|
209
|
+
users: new Map()
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
await program.parseAsync(['node', 'slack-cli', 'history', '-c', 'times_sakashi']);
|
|
213
|
+
|
|
214
|
+
expect(mockSlackClient.getHistory).toHaveBeenCalledWith('times_sakashi', { limit: 10 });
|
|
215
|
+
expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('No messages found'));
|
|
216
|
+
});
|
|
179
217
|
});
|
|
180
218
|
|
|
181
219
|
describe('output formatting', () => {
|
|
@@ -166,5 +166,104 @@ describe('SlackApiClient', () => {
|
|
|
166
166
|
limit: 100
|
|
167
167
|
})).rejects.toThrow('invalid_auth');
|
|
168
168
|
});
|
|
169
|
+
|
|
170
|
+
it('should handle pagination when listing channels', async () => {
|
|
171
|
+
// First page
|
|
172
|
+
vi.mocked(mockWebClient.conversations.list).mockResolvedValueOnce({
|
|
173
|
+
ok: true,
|
|
174
|
+
channels: [
|
|
175
|
+
{ id: 'C001', name: 'channel1', is_private: false },
|
|
176
|
+
{ id: 'C002', name: 'channel2', is_private: false }
|
|
177
|
+
],
|
|
178
|
+
response_metadata: {
|
|
179
|
+
next_cursor: 'cursor123'
|
|
180
|
+
}
|
|
181
|
+
} as any);
|
|
182
|
+
|
|
183
|
+
// Second page
|
|
184
|
+
vi.mocked(mockWebClient.conversations.list).mockResolvedValueOnce({
|
|
185
|
+
ok: true,
|
|
186
|
+
channels: [
|
|
187
|
+
{ id: 'C003', name: 'channel3', is_private: false },
|
|
188
|
+
{ id: 'C004', name: 'channel4', is_private: false }
|
|
189
|
+
],
|
|
190
|
+
response_metadata: {
|
|
191
|
+
next_cursor: 'cursor456'
|
|
192
|
+
}
|
|
193
|
+
} as any);
|
|
194
|
+
|
|
195
|
+
// Third page (last page)
|
|
196
|
+
vi.mocked(mockWebClient.conversations.list).mockResolvedValueOnce({
|
|
197
|
+
ok: true,
|
|
198
|
+
channels: [
|
|
199
|
+
{ id: 'C005', name: 'channel5', is_private: false }
|
|
200
|
+
],
|
|
201
|
+
response_metadata: {
|
|
202
|
+
next_cursor: ''
|
|
203
|
+
}
|
|
204
|
+
} as any);
|
|
205
|
+
|
|
206
|
+
const result = await client.listChannels({
|
|
207
|
+
types: 'public_channel',
|
|
208
|
+
exclude_archived: true,
|
|
209
|
+
limit: 2
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Should have called the API 3 times
|
|
213
|
+
expect(mockWebClient.conversations.list).toHaveBeenCalledTimes(3);
|
|
214
|
+
|
|
215
|
+
// First call
|
|
216
|
+
expect(mockWebClient.conversations.list).toHaveBeenNthCalledWith(1, {
|
|
217
|
+
types: 'public_channel',
|
|
218
|
+
exclude_archived: true,
|
|
219
|
+
limit: 2,
|
|
220
|
+
cursor: undefined
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Second call
|
|
224
|
+
expect(mockWebClient.conversations.list).toHaveBeenNthCalledWith(2, {
|
|
225
|
+
types: 'public_channel',
|
|
226
|
+
exclude_archived: true,
|
|
227
|
+
limit: 2,
|
|
228
|
+
cursor: 'cursor123'
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Third call
|
|
232
|
+
expect(mockWebClient.conversations.list).toHaveBeenNthCalledWith(3, {
|
|
233
|
+
types: 'public_channel',
|
|
234
|
+
exclude_archived: true,
|
|
235
|
+
limit: 2,
|
|
236
|
+
cursor: 'cursor456'
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Should return all channels
|
|
240
|
+
expect(result).toHaveLength(5);
|
|
241
|
+
expect(result[0].name).toBe('channel1');
|
|
242
|
+
expect(result[1].name).toBe('channel2');
|
|
243
|
+
expect(result[2].name).toBe('channel3');
|
|
244
|
+
expect(result[3].name).toBe('channel4');
|
|
245
|
+
expect(result[4].name).toBe('channel5');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should handle empty cursor in pagination', async () => {
|
|
249
|
+
vi.mocked(mockWebClient.conversations.list).mockResolvedValueOnce({
|
|
250
|
+
ok: true,
|
|
251
|
+
channels: [
|
|
252
|
+
{ id: 'C001', name: 'channel1', is_private: false }
|
|
253
|
+
],
|
|
254
|
+
response_metadata: {
|
|
255
|
+
// No next_cursor field
|
|
256
|
+
}
|
|
257
|
+
} as any);
|
|
258
|
+
|
|
259
|
+
const result = await client.listChannels({
|
|
260
|
+
types: 'public_channel',
|
|
261
|
+
exclude_archived: true,
|
|
262
|
+
limit: 100
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
expect(mockWebClient.conversations.list).toHaveBeenCalledTimes(1);
|
|
266
|
+
expect(result).toHaveLength(1);
|
|
267
|
+
});
|
|
169
268
|
});
|
|
170
269
|
});
|