@urugus/slack-cli 0.2.12 → 0.3.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.
Files changed (123) hide show
  1. package/README.md +45 -0
  2. package/dist/commands/history-display.d.ts +5 -1
  3. package/dist/commands/history-display.d.ts.map +1 -1
  4. package/dist/commands/history-display.js +3 -3
  5. package/dist/commands/history-display.js.map +1 -1
  6. package/dist/commands/history.d.ts.map +1 -1
  7. package/dist/commands/history.js +28 -11
  8. package/dist/commands/history.js.map +1 -1
  9. package/dist/commands/search.d.ts +3 -0
  10. package/dist/commands/search.d.ts.map +1 -0
  11. package/dist/commands/search.js +51 -0
  12. package/dist/commands/search.js.map +1 -0
  13. package/dist/index.js +2 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/types/commands.d.ts +10 -0
  16. package/dist/types/commands.d.ts.map +1 -1
  17. package/dist/utils/constants.d.ts +5 -0
  18. package/dist/utils/constants.d.ts.map +1 -1
  19. package/dist/utils/constants.js +5 -0
  20. package/dist/utils/constants.js.map +1 -1
  21. package/dist/utils/formatters/search-formatters.d.ts +10 -0
  22. package/dist/utils/formatters/search-formatters.d.ts.map +1 -0
  23. package/dist/utils/formatters/search-formatters.js +91 -0
  24. package/dist/utils/formatters/search-formatters.js.map +1 -0
  25. package/dist/utils/slack-api-client.d.ts +5 -0
  26. package/dist/utils/slack-api-client.d.ts.map +1 -1
  27. package/dist/utils/slack-api-client.js +8 -0
  28. package/dist/utils/slack-api-client.js.map +1 -1
  29. package/dist/utils/slack-operations/index.d.ts +1 -0
  30. package/dist/utils/slack-operations/index.d.ts.map +1 -1
  31. package/dist/utils/slack-operations/index.js +3 -1
  32. package/dist/utils/slack-operations/index.js.map +1 -1
  33. package/dist/utils/slack-operations/message-operations.d.ts +1 -0
  34. package/dist/utils/slack-operations/message-operations.d.ts.map +1 -1
  35. package/dist/utils/slack-operations/message-operations.js +21 -0
  36. package/dist/utils/slack-operations/message-operations.js.map +1 -1
  37. package/dist/utils/slack-operations/search-operations.d.ts +29 -0
  38. package/dist/utils/slack-operations/search-operations.d.ts.map +1 -0
  39. package/dist/utils/slack-operations/search-operations.js +37 -0
  40. package/dist/utils/slack-operations/search-operations.js.map +1 -0
  41. package/dist/utils/validators.d.ts +16 -0
  42. package/dist/utils/validators.d.ts.map +1 -1
  43. package/dist/utils/validators.js +50 -0
  44. package/dist/utils/validators.js.map +1 -1
  45. package/package.json +5 -2
  46. package/.claude/settings.local.json +0 -75
  47. package/.github/dependabot.yml +0 -18
  48. package/.github/workflows/ci.yml +0 -70
  49. package/.github/workflows/pr-validation.yml +0 -41
  50. package/.prettierignore +0 -11
  51. package/.prettierrc +0 -10
  52. package/CHANGELOG.md +0 -61
  53. package/CLAUDE.md +0 -16
  54. package/eslint.config.js +0 -38
  55. package/src/commands/channels.ts +0 -50
  56. package/src/commands/config-subcommands.ts +0 -63
  57. package/src/commands/config.ts +0 -50
  58. package/src/commands/history-display.ts +0 -19
  59. package/src/commands/history-validators.ts +0 -46
  60. package/src/commands/history.ts +0 -61
  61. package/src/commands/scheduled.ts +0 -71
  62. package/src/commands/send.ts +0 -69
  63. package/src/commands/unread.ts +0 -122
  64. package/src/index.ts +0 -27
  65. package/src/types/commands.ts +0 -58
  66. package/src/types/config.ts +0 -20
  67. package/src/utils/channel-formatter.ts +0 -45
  68. package/src/utils/channel-resolver.ts +0 -82
  69. package/src/utils/client-factory.ts +0 -10
  70. package/src/utils/command-wrapper.ts +0 -27
  71. package/src/utils/config/config-file-manager.ts +0 -56
  72. package/src/utils/config/profile-manager.ts +0 -79
  73. package/src/utils/config/token-crypto-service.ts +0 -80
  74. package/src/utils/config-helper.ts +0 -21
  75. package/src/utils/constants.ts +0 -78
  76. package/src/utils/date-utils.ts +0 -8
  77. package/src/utils/error-utils.ts +0 -6
  78. package/src/utils/errors.ts +0 -33
  79. package/src/utils/format-utils.ts +0 -9
  80. package/src/utils/formatters/base-formatter.ts +0 -34
  81. package/src/utils/formatters/channel-formatters.ts +0 -71
  82. package/src/utils/formatters/channels-list-formatters.ts +0 -55
  83. package/src/utils/formatters/history-formatters.ts +0 -123
  84. package/src/utils/formatters/message-formatters.ts +0 -85
  85. package/src/utils/mention-utils.ts +0 -47
  86. package/src/utils/option-parsers.ts +0 -100
  87. package/src/utils/profile-config.ts +0 -161
  88. package/src/utils/schedule-utils.ts +0 -41
  89. package/src/utils/slack-api-client.ts +0 -135
  90. package/src/utils/slack-operations/base-client.ts +0 -30
  91. package/src/utils/slack-operations/channel-operations.ts +0 -161
  92. package/src/utils/slack-operations/index.ts +0 -3
  93. package/src/utils/slack-operations/message-operations.ts +0 -176
  94. package/src/utils/slack-patterns.ts +0 -9
  95. package/src/utils/token-utils.ts +0 -17
  96. package/src/utils/validators.ts +0 -263
  97. package/tests/commands/channels.test.ts +0 -250
  98. package/tests/commands/config.test.ts +0 -158
  99. package/tests/commands/history.test.ts +0 -403
  100. package/tests/commands/scheduled.test.ts +0 -131
  101. package/tests/commands/send.test.ts +0 -414
  102. package/tests/commands/unread.test.ts +0 -492
  103. package/tests/index.test.ts +0 -40
  104. package/tests/test-utils.ts +0 -28
  105. package/tests/utils/channel-resolver.test.ts +0 -161
  106. package/tests/utils/config/config-file-manager.test.ts +0 -118
  107. package/tests/utils/config/profile-manager.test.ts +0 -266
  108. package/tests/utils/config/token-crypto-service.test.ts +0 -98
  109. package/tests/utils/config.test.ts +0 -400
  110. package/tests/utils/date-utils.test.ts +0 -30
  111. package/tests/utils/error-utils.test.ts +0 -34
  112. package/tests/utils/format-utils.test.ts +0 -61
  113. package/tests/utils/mention-utils.test.ts +0 -100
  114. package/tests/utils/option-parsers.test.ts +0 -173
  115. package/tests/utils/profile-config.test.ts +0 -282
  116. package/tests/utils/schedule-utils.test.ts +0 -63
  117. package/tests/utils/slack-api-client.test.ts +0 -313
  118. package/tests/utils/slack-operations/channel-operations.test.ts +0 -248
  119. package/tests/utils/slack-operations/message-operations.test.ts +0 -163
  120. package/tests/utils/token-utils.test.ts +0 -33
  121. package/tests/utils/validators.test.ts +0 -307
  122. package/tsconfig.json +0 -22
  123. package/vitest.config.ts +0 -27
@@ -1,313 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { SlackApiClient } from '../../src/utils/slack-api-client';
3
- import { WebClient, LogLevel } from '@slack/web-api';
4
-
5
- vi.mock('@slack/web-api');
6
-
7
- describe('SlackApiClient', () => {
8
- let client: SlackApiClient;
9
- let mockWebClient: any;
10
-
11
- beforeEach(() => {
12
- vi.clearAllMocks();
13
- mockWebClient = {
14
- chat: {
15
- postMessage: vi.fn(),
16
- scheduleMessage: vi.fn(),
17
- scheduledMessages: {
18
- list: vi.fn(),
19
- },
20
- },
21
- conversations: {
22
- list: vi.fn(),
23
- info: vi.fn(),
24
- },
25
- users: {
26
- conversations: vi.fn(),
27
- },
28
- };
29
- vi.mocked(WebClient).mockReturnValue(mockWebClient);
30
- client = new SlackApiClient('test-token');
31
- });
32
-
33
- describe('constructor', () => {
34
- it('should create WebClient with provided token', () => {
35
- expect(WebClient).toHaveBeenCalledWith('test-token', {
36
- retryConfig: {
37
- retries: 0, // Disabled to handle rate limits manually
38
- },
39
- logLevel: LogLevel.ERROR,
40
- });
41
- });
42
- });
43
-
44
- describe('sendMessage', () => {
45
- it('should send message to channel', async () => {
46
- const mockResponse = { ok: true, ts: '1234567890.123456' };
47
- vi.mocked(mockWebClient.chat.postMessage).mockResolvedValue(mockResponse as any);
48
-
49
- const result = await client.sendMessage('general', 'Hello, World!');
50
-
51
- expect(mockWebClient.chat.postMessage).toHaveBeenCalledWith({
52
- channel: 'general',
53
- text: 'Hello, World!',
54
- });
55
- expect(result).toEqual(mockResponse);
56
- });
57
-
58
- it('should handle channel ID format', async () => {
59
- const mockResponse = { ok: true, ts: '1234567890.123456' };
60
- vi.mocked(mockWebClient.chat.postMessage).mockResolvedValue(mockResponse as any);
61
-
62
- await client.sendMessage('C1234567890', 'Hello!');
63
-
64
- expect(mockWebClient.chat.postMessage).toHaveBeenCalledWith({
65
- channel: 'C1234567890',
66
- text: 'Hello!',
67
- });
68
- });
69
-
70
- it('should handle multi-line messages', async () => {
71
- const mockResponse = { ok: true, ts: '1234567890.123456' };
72
- vi.mocked(mockWebClient.chat.postMessage).mockResolvedValue(mockResponse as any);
73
-
74
- const multiLineMessage = 'Line 1\nLine 2\nLine 3';
75
- await client.sendMessage('general', multiLineMessage);
76
-
77
- expect(mockWebClient.chat.postMessage).toHaveBeenCalledWith({
78
- channel: 'general',
79
- text: multiLineMessage,
80
- });
81
- });
82
-
83
- it('should throw error on API failure', async () => {
84
- const mockError = new Error('channel_not_found');
85
- vi.mocked(mockWebClient.chat.postMessage).mockRejectedValue(mockError);
86
-
87
- await expect(client.sendMessage('nonexistent', 'Hello')).rejects.toThrow('channel_not_found');
88
- });
89
- });
90
-
91
- describe('scheduleMessage', () => {
92
- it('should schedule message to channel', async () => {
93
- const mockResponse = { ok: true, scheduled_message_id: 'Q123', post_at: 1770855000 };
94
- vi.mocked(mockWebClient.chat.scheduleMessage).mockResolvedValue(mockResponse as any);
95
-
96
- const result = await client.scheduleMessage('general', 'Hello, future!', 1770855000);
97
-
98
- expect(mockWebClient.chat.scheduleMessage).toHaveBeenCalledWith({
99
- channel: 'general',
100
- text: 'Hello, future!',
101
- post_at: 1770855000,
102
- });
103
- expect(result).toEqual(mockResponse);
104
- });
105
- });
106
-
107
- describe('listScheduledMessages', () => {
108
- it('should list scheduled messages', async () => {
109
- const mockResponse = {
110
- ok: true,
111
- scheduled_messages: [
112
- { id: 'Q123', channel_id: 'C123', post_at: 1770855000, date_created: 1770854400 },
113
- ],
114
- };
115
- vi.mocked(mockWebClient.chat.scheduledMessages.list).mockResolvedValue(mockResponse as any);
116
-
117
- const result = await client.listScheduledMessages();
118
-
119
- expect(mockWebClient.chat.scheduledMessages.list).toHaveBeenCalledWith({ limit: 50 });
120
- expect(result).toEqual(mockResponse.scheduled_messages);
121
- });
122
- });
123
-
124
- describe('listChannels', () => {
125
- it('should list channels with default options', async () => {
126
- const mockChannels = [
127
- {
128
- id: 'C1234567890',
129
- name: 'general',
130
- is_channel: true,
131
- is_private: false,
132
- num_members: 150,
133
- created: 1579075200,
134
- purpose: { value: 'Company announcements' },
135
- },
136
- ];
137
- vi.mocked(mockWebClient.conversations.list).mockResolvedValue({
138
- ok: true,
139
- channels: mockChannels,
140
- } as any);
141
-
142
- const result = await client.listChannels({
143
- types: 'public_channel',
144
- exclude_archived: true,
145
- limit: 100,
146
- });
147
-
148
- expect(mockWebClient.conversations.list).toHaveBeenCalledWith({
149
- types: 'public_channel',
150
- exclude_archived: true,
151
- limit: 100,
152
- });
153
- expect(result).toEqual(mockChannels);
154
- });
155
-
156
- it('should handle private channels', async () => {
157
- const mockChannels = [
158
- {
159
- id: 'G1234567890',
160
- name: 'private-channel',
161
- is_group: true,
162
- is_private: true,
163
- num_members: 10,
164
- created: 1579075200,
165
- purpose: { value: 'Private discussions' },
166
- },
167
- ];
168
- vi.mocked(mockWebClient.conversations.list).mockResolvedValue({
169
- ok: true,
170
- channels: mockChannels,
171
- } as any);
172
-
173
- const result = await client.listChannels({
174
- types: 'private_channel',
175
- exclude_archived: true,
176
- limit: 50,
177
- });
178
-
179
- expect(mockWebClient.conversations.list).toHaveBeenCalledWith({
180
- types: 'private_channel',
181
- exclude_archived: true,
182
- limit: 50,
183
- });
184
- expect(result).toEqual(mockChannels);
185
- });
186
-
187
- it('should handle multiple channel types', async () => {
188
- vi.mocked(mockWebClient.conversations.list).mockResolvedValue({
189
- ok: true,
190
- channels: [],
191
- } as any);
192
-
193
- await client.listChannels({
194
- types: 'public_channel,private_channel,im',
195
- exclude_archived: false,
196
- limit: 200,
197
- });
198
-
199
- expect(mockWebClient.conversations.list).toHaveBeenCalledWith({
200
- types: 'public_channel,private_channel,im',
201
- exclude_archived: false,
202
- limit: 200,
203
- });
204
- });
205
-
206
- it('should handle API errors', async () => {
207
- vi.mocked(mockWebClient.conversations.list).mockRejectedValue(new Error('invalid_auth'));
208
-
209
- await expect(
210
- client.listChannels({
211
- types: 'public_channel',
212
- exclude_archived: true,
213
- limit: 100,
214
- })
215
- ).rejects.toThrow('invalid_auth');
216
- });
217
-
218
- it('should handle pagination when listing channels', async () => {
219
- // First page
220
- vi.mocked(mockWebClient.conversations.list).mockResolvedValueOnce({
221
- ok: true,
222
- channels: [
223
- { id: 'C001', name: 'channel1', is_private: false },
224
- { id: 'C002', name: 'channel2', is_private: false },
225
- ],
226
- response_metadata: {
227
- next_cursor: 'cursor123',
228
- },
229
- } as any);
230
-
231
- // Second page
232
- vi.mocked(mockWebClient.conversations.list).mockResolvedValueOnce({
233
- ok: true,
234
- channels: [
235
- { id: 'C003', name: 'channel3', is_private: false },
236
- { id: 'C004', name: 'channel4', is_private: false },
237
- ],
238
- response_metadata: {
239
- next_cursor: 'cursor456',
240
- },
241
- } as any);
242
-
243
- // Third page (last page)
244
- vi.mocked(mockWebClient.conversations.list).mockResolvedValueOnce({
245
- ok: true,
246
- channels: [{ id: 'C005', name: 'channel5', is_private: false }],
247
- response_metadata: {
248
- next_cursor: '',
249
- },
250
- } as any);
251
-
252
- const result = await client.listChannels({
253
- types: 'public_channel',
254
- exclude_archived: true,
255
- limit: 2,
256
- });
257
-
258
- // Should have called the API 3 times
259
- expect(mockWebClient.conversations.list).toHaveBeenCalledTimes(3);
260
-
261
- // First call
262
- expect(mockWebClient.conversations.list).toHaveBeenNthCalledWith(1, {
263
- types: 'public_channel',
264
- exclude_archived: true,
265
- limit: 2,
266
- cursor: undefined,
267
- });
268
-
269
- // Second call
270
- expect(mockWebClient.conversations.list).toHaveBeenNthCalledWith(2, {
271
- types: 'public_channel',
272
- exclude_archived: true,
273
- limit: 2,
274
- cursor: 'cursor123',
275
- });
276
-
277
- // Third call
278
- expect(mockWebClient.conversations.list).toHaveBeenNthCalledWith(3, {
279
- types: 'public_channel',
280
- exclude_archived: true,
281
- limit: 2,
282
- cursor: 'cursor456',
283
- });
284
-
285
- // Should return all channels
286
- expect(result).toHaveLength(5);
287
- expect(result[0].name).toBe('channel1');
288
- expect(result[1].name).toBe('channel2');
289
- expect(result[2].name).toBe('channel3');
290
- expect(result[3].name).toBe('channel4');
291
- expect(result[4].name).toBe('channel5');
292
- });
293
-
294
- it('should handle empty cursor in pagination', async () => {
295
- vi.mocked(mockWebClient.conversations.list).mockResolvedValueOnce({
296
- ok: true,
297
- channels: [{ id: 'C001', name: 'channel1', is_private: false }],
298
- response_metadata: {
299
- // No next_cursor field
300
- },
301
- } as any);
302
-
303
- const result = await client.listChannels({
304
- types: 'public_channel',
305
- exclude_archived: true,
306
- limit: 100,
307
- });
308
-
309
- expect(mockWebClient.conversations.list).toHaveBeenCalledTimes(1);
310
- expect(result).toHaveLength(1);
311
- });
312
- });
313
- });
@@ -1,248 +0,0 @@
1
- import { vi, describe, it, expect, beforeEach } from 'vitest';
2
- import { ChannelOperations } from '../../../src/utils/slack-operations/channel-operations';
3
-
4
- vi.mock('@slack/web-api', () => ({
5
- WebClient: vi.fn().mockImplementation(() => ({
6
- conversations: {
7
- list: vi.fn(),
8
- info: vi.fn(),
9
- history: vi.fn(),
10
- },
11
- })),
12
- LogLevel: {
13
- ERROR: 'error',
14
- },
15
- }));
16
-
17
- vi.mock('p-limit', () => ({
18
- default: () => (fn: any) => fn(),
19
- }));
20
-
21
- describe('ChannelOperations', () => {
22
- let channelOps: ChannelOperations;
23
- let mockClient: any;
24
-
25
- beforeEach(() => {
26
- vi.clearAllMocks();
27
- mockClient = {
28
- conversations: {
29
- list: vi.fn(),
30
- info: vi.fn(),
31
- history: vi.fn(),
32
- },
33
- };
34
- // Create instance with mocked token
35
- channelOps = new ChannelOperations('test-token');
36
- // Replace the client with our mock
37
- (channelOps as any).client = mockClient;
38
- });
39
-
40
- describe('listUnreadChannels', () => {
41
- it('should detect unread messages when last_read is present', async () => {
42
- // Mock conversations.list response
43
- mockClient.conversations.list.mockResolvedValue({
44
- channels: [
45
- { id: 'C123', name: 'general' },
46
- { id: 'C456', name: 'random' },
47
- ],
48
- });
49
-
50
- // Mock conversations.info responses
51
- mockClient.conversations.info
52
- .mockResolvedValueOnce({
53
- channel: {
54
- id: 'C123',
55
- name: 'general',
56
- last_read: '1234567890.000100',
57
- },
58
- })
59
- .mockResolvedValueOnce({
60
- channel: {
61
- id: 'C456',
62
- name: 'random',
63
- last_read: '1234567890.000200',
64
- },
65
- });
66
-
67
- // Mock conversations.history responses
68
- mockClient.conversations.history
69
- // First call for C123 - check latest message
70
- .mockResolvedValueOnce({
71
- messages: [{ ts: '1234567890.000200' }], // Newer than last_read
72
- })
73
- // Second call for C123 - get unread messages
74
- .mockResolvedValueOnce({
75
- messages: [
76
- { ts: '1234567890.000200' },
77
- { ts: '1234567890.000150' },
78
- ],
79
- })
80
- // Third call for C456 - check latest message
81
- .mockResolvedValueOnce({
82
- messages: [{ ts: '1234567890.000100' }], // Older than last_read
83
- });
84
-
85
- const result = await channelOps.listUnreadChannels();
86
-
87
- expect(result).toHaveLength(1);
88
- expect(result[0]).toMatchObject({
89
- id: 'C123',
90
- name: 'general',
91
- unread_count: 2,
92
- unread_count_display: 2,
93
- last_read: '1234567890.000100',
94
- });
95
- });
96
-
97
- it('should count all messages as unread when last_read is not present', async () => {
98
- // Mock conversations.list response
99
- mockClient.conversations.list.mockResolvedValue({
100
- channels: [{ id: 'C789', name: 'no-read-channel' }],
101
- });
102
-
103
- // Mock conversations.info response - no last_read
104
- mockClient.conversations.info.mockResolvedValue({
105
- channel: {
106
- id: 'C789',
107
- name: 'no-read-channel',
108
- // last_read is missing
109
- },
110
- });
111
-
112
- // Mock conversations.history responses
113
- mockClient.conversations.history
114
- // First call - check if channel has messages
115
- .mockResolvedValueOnce({
116
- messages: [{ ts: '1234567890.000300' }],
117
- })
118
- // Second call - get all messages (up to 100)
119
- .mockResolvedValueOnce({
120
- messages: [
121
- { ts: '1234567890.000300' },
122
- { ts: '1234567890.000200' },
123
- { ts: '1234567890.000100' },
124
- ],
125
- });
126
-
127
- const result = await channelOps.listUnreadChannels();
128
-
129
- expect(result).toHaveLength(1);
130
- expect(result[0]).toMatchObject({
131
- id: 'C789',
132
- name: 'no-read-channel',
133
- unread_count: 3,
134
- unread_count_display: 3,
135
- last_read: undefined,
136
- });
137
- });
138
-
139
- it('should skip channels with no messages', async () => {
140
- mockClient.conversations.list.mockResolvedValue({
141
- channels: [{ id: 'C999', name: 'empty-channel' }],
142
- });
143
-
144
- mockClient.conversations.info.mockResolvedValue({
145
- channel: {
146
- id: 'C999',
147
- name: 'empty-channel',
148
- last_read: '1234567890.000100',
149
- },
150
- });
151
-
152
- // No messages in channel
153
- mockClient.conversations.history.mockResolvedValue({
154
- messages: [],
155
- });
156
-
157
- const result = await channelOps.listUnreadChannels();
158
-
159
- expect(result).toHaveLength(0);
160
- });
161
-
162
- it('should skip channels with all messages read', async () => {
163
- mockClient.conversations.list.mockResolvedValue({
164
- channels: [{ id: 'C111', name: 'all-read' }],
165
- });
166
-
167
- mockClient.conversations.info.mockResolvedValue({
168
- channel: {
169
- id: 'C111',
170
- name: 'all-read',
171
- last_read: '1234567890.000200',
172
- },
173
- });
174
-
175
- // First call to get latest message (limit: 1)
176
- mockClient.conversations.history.mockResolvedValueOnce({
177
- messages: [{ ts: '1234567890.000100' }],
178
- });
179
-
180
- // Second call to get messages after last_read (should be empty)
181
- mockClient.conversations.history.mockResolvedValueOnce({
182
- messages: [],
183
- });
184
-
185
- const result = await channelOps.listUnreadChannels();
186
-
187
- expect(result).toHaveLength(0);
188
- });
189
-
190
- it('should handle API errors gracefully', async () => {
191
- mockClient.conversations.list.mockResolvedValue({
192
- channels: [
193
- { id: 'C222', name: 'error-channel' },
194
- { id: 'C333', name: 'good-channel' },
195
- ],
196
- });
197
-
198
- // First channel throws error
199
- mockClient.conversations.info
200
- .mockRejectedValueOnce(new Error('API Error'))
201
- .mockResolvedValueOnce({
202
- channel: {
203
- id: 'C333',
204
- name: 'good-channel',
205
- last_read: '1234567890.000100',
206
- },
207
- });
208
-
209
- mockClient.conversations.history
210
- .mockResolvedValueOnce({
211
- messages: [{ ts: '1234567890.000200' }],
212
- })
213
- .mockResolvedValueOnce({
214
- messages: [{ ts: '1234567890.000200' }],
215
- });
216
-
217
- const result = await channelOps.listUnreadChannels();
218
-
219
- // Should skip the error channel and process the good one
220
- expect(result).toHaveLength(1);
221
- expect(result[0].id).toBe('C333');
222
- });
223
-
224
- it('should handle rate limiting with delay', async () => {
225
- const delaySpy = vi.spyOn(channelOps as any, 'delay');
226
-
227
- mockClient.conversations.list.mockResolvedValue({
228
- channels: [
229
- { id: 'C444', name: 'channel1' },
230
- { id: 'C555', name: 'channel2' },
231
- ],
232
- });
233
-
234
- mockClient.conversations.info.mockResolvedValue({
235
- channel: { id: 'C444', name: 'channel1' },
236
- });
237
-
238
- mockClient.conversations.history.mockResolvedValue({
239
- messages: [],
240
- });
241
-
242
- await channelOps.listUnreadChannels();
243
-
244
- // Verify delay was called between API calls
245
- expect(delaySpy).toHaveBeenCalledWith(100);
246
- });
247
- });
248
- });