@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,492 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { setupUnreadCommand } from '../../src/commands/unread';
3
- import { SlackApiClient } from '../../src/utils/slack-api-client';
4
- import { ProfileConfigManager } from '../../src/utils/profile-config';
5
- import { setupMockConsole, createTestProgram, restoreMocks } from '../test-utils';
6
- import { ERROR_MESSAGES } from '../../src/utils/constants';
7
- import chalk from 'chalk';
8
-
9
- vi.mock('../../src/utils/slack-api-client');
10
- vi.mock('../../src/utils/profile-config');
11
-
12
- describe('unread command', () => {
13
- let program: any;
14
- let mockSlackClient: SlackApiClient;
15
- let mockConfigManager: ProfileConfigManager;
16
- let mockConsole: any;
17
-
18
- beforeEach(() => {
19
- vi.clearAllMocks();
20
-
21
- mockConfigManager = new ProfileConfigManager();
22
- vi.mocked(ProfileConfigManager).mockReturnValue(mockConfigManager);
23
-
24
- mockSlackClient = new SlackApiClient('test-token');
25
- vi.mocked(SlackApiClient).mockReturnValue(mockSlackClient);
26
-
27
- mockConsole = setupMockConsole();
28
- program = createTestProgram();
29
- program.addCommand(setupUnreadCommand());
30
- });
31
-
32
- afterEach(() => {
33
- restoreMocks();
34
- });
35
-
36
- const mockChannelsWithUnread = [
37
- {
38
- id: 'C123',
39
- name: 'general',
40
- is_channel: true,
41
- is_member: true,
42
- is_archived: false,
43
- unread_count: 5,
44
- unread_count_display: 5,
45
- last_read: '1705286300.000000',
46
- },
47
- {
48
- id: 'C456',
49
- name: 'random',
50
- is_channel: true,
51
- is_member: true,
52
- is_archived: false,
53
- unread_count: 2,
54
- unread_count_display: 2,
55
- last_read: '1705286400.000000',
56
- },
57
- {
58
- id: 'C789',
59
- name: 'dev',
60
- is_channel: true,
61
- is_member: true,
62
- is_archived: false,
63
- unread_count: 0,
64
- unread_count_display: 0,
65
- last_read: '1705286500.000000',
66
- },
67
- ];
68
-
69
- const mockUnreadMessages = [
70
- {
71
- ts: '1705286400.000001',
72
- user: 'U123',
73
- text: 'Hello world',
74
- type: 'message',
75
- },
76
- {
77
- ts: '1705286500.000002',
78
- user: 'U456',
79
- text: 'Test message',
80
- type: 'message',
81
- },
82
- ];
83
-
84
- describe('basic functionality', () => {
85
- it('should display unread counts in table format by default', async () => {
86
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
87
- token: 'test-token',
88
- updatedAt: new Date().toISOString()
89
- });
90
- vi.mocked(mockSlackClient.listUnreadChannels).mockResolvedValue(
91
- mockChannelsWithUnread.filter(ch => (ch.unread_count_display || 0) > 0)
92
- );
93
-
94
- await program.parseAsync(['node', 'slack-cli', 'unread']);
95
-
96
- expect(mockSlackClient.listUnreadChannels).toHaveBeenCalled();
97
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Channel'));
98
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Unread'));
99
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('#general'));
100
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('5'));
101
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('#random'));
102
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('2'));
103
- });
104
-
105
- it('should display only count when --count-only is specified', async () => {
106
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
107
- token: 'test-token',
108
- updatedAt: new Date().toISOString()
109
- });
110
- vi.mocked(mockSlackClient.listUnreadChannels).mockResolvedValue(
111
- mockChannelsWithUnread.filter(ch => (ch.unread_count_display || 0) > 0)
112
- );
113
-
114
- await program.parseAsync(['node', 'slack-cli', 'unread', '--count-only']);
115
-
116
- expect(mockConsole.logSpy).toHaveBeenCalledWith('#general: 5');
117
- expect(mockConsole.logSpy).toHaveBeenCalledWith('#random: 2');
118
- expect(mockConsole.logSpy).toHaveBeenCalledWith(chalk.bold('Total: 7 unread messages'));
119
- });
120
-
121
- it('should display in JSON format when specified', async () => {
122
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
123
- token: 'test-token',
124
- updatedAt: new Date().toISOString()
125
- });
126
- vi.mocked(mockSlackClient.listUnreadChannels).mockResolvedValue(
127
- mockChannelsWithUnread.filter(ch => (ch.unread_count_display || 0) > 0)
128
- );
129
-
130
- await program.parseAsync(['node', 'slack-cli', 'unread', '--format', 'json']);
131
-
132
- const expectedOutput = [
133
- { channel: '#general', channelId: 'C123', unreadCount: 5 },
134
- { channel: '#random', channelId: 'C456', unreadCount: 2 },
135
- ];
136
- expect(mockConsole.logSpy).toHaveBeenCalledWith(JSON.stringify(expectedOutput, null, 2));
137
- });
138
-
139
- it('should display in simple format when specified', async () => {
140
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
141
- token: 'test-token',
142
- updatedAt: new Date().toISOString()
143
- });
144
- vi.mocked(mockSlackClient.listUnreadChannels).mockResolvedValue(
145
- mockChannelsWithUnread.filter(ch => (ch.unread_count_display || 0) > 0)
146
- );
147
-
148
- await program.parseAsync(['node', 'slack-cli', 'unread', '--format', 'simple']);
149
-
150
- expect(mockConsole.logSpy).toHaveBeenCalledWith('#general (5)');
151
- expect(mockConsole.logSpy).toHaveBeenCalledWith('#random (2)');
152
- });
153
- });
154
-
155
- describe('channel filtering', () => {
156
- it('should filter by specific channel when --channel is specified', async () => {
157
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
158
- token: 'test-token',
159
- updatedAt: new Date().toISOString()
160
- });
161
- vi.mocked(mockSlackClient.getChannelUnread).mockResolvedValue({
162
- channel: mockChannelsWithUnread[0],
163
- messages: mockUnreadMessages,
164
- users: new Map([
165
- ['U123', 'john.doe'],
166
- ['U456', 'jane.smith']
167
- ])
168
- });
169
-
170
- await program.parseAsync(['node', 'slack-cli', 'unread', '--channel', 'general']);
171
-
172
- expect(mockSlackClient.getChannelUnread).toHaveBeenCalledWith('general');
173
- expect(mockConsole.logSpy).toHaveBeenCalledWith(chalk.bold(`#general: 5 unread messages`));
174
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Hello world'));
175
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Test message'));
176
- });
177
-
178
- it('should display channel unread in JSON format when specified', async () => {
179
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
180
- token: 'test-token',
181
- updatedAt: new Date().toISOString()
182
- });
183
- vi.mocked(mockSlackClient.getChannelUnread).mockResolvedValue({
184
- channel: mockChannelsWithUnread[0],
185
- messages: mockUnreadMessages,
186
- users: new Map([
187
- ['U123', 'john.doe'],
188
- ['U456', 'jane.smith']
189
- ])
190
- });
191
-
192
- await program.parseAsync(['node', 'slack-cli', 'unread', '--channel', 'general', '--format', 'json']);
193
-
194
- expect(mockSlackClient.getChannelUnread).toHaveBeenCalledWith('general');
195
-
196
- const logCall = mockConsole.logSpy.mock.calls[0][0];
197
- const parsed = JSON.parse(logCall);
198
-
199
- expect(parsed).toEqual({
200
- channel: '#general',
201
- channelId: 'C123',
202
- unreadCount: 5,
203
- messages: [
204
- {
205
- timestamp: expect.any(String),
206
- author: 'john.doe',
207
- text: 'Hello world'
208
- },
209
- {
210
- timestamp: expect.any(String),
211
- author: 'jane.smith',
212
- text: 'Test message'
213
- }
214
- ]
215
- });
216
- });
217
-
218
- it('should display channel unread in simple format when specified', async () => {
219
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
220
- token: 'test-token',
221
- updatedAt: new Date().toISOString()
222
- });
223
- vi.mocked(mockSlackClient.getChannelUnread).mockResolvedValue({
224
- channel: mockChannelsWithUnread[0],
225
- messages: mockUnreadMessages,
226
- users: new Map([
227
- ['U123', 'john.doe'],
228
- ['U456', 'jane.smith']
229
- ])
230
- });
231
-
232
- await program.parseAsync(['node', 'slack-cli', 'unread', '--channel', 'general', '--format', 'simple']);
233
-
234
- expect(mockSlackClient.getChannelUnread).toHaveBeenCalledWith('general');
235
- expect(mockConsole.logSpy).toHaveBeenCalledWith('#general (5)');
236
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringMatching(/^\[.*\] john\.doe: Hello world$/));
237
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringMatching(/^\[.*\] jane\.smith: Test message$/));
238
- });
239
-
240
- it('should show only count for channel when --count-only is specified', async () => {
241
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
242
- token: 'test-token',
243
- updatedAt: new Date().toISOString()
244
- });
245
- vi.mocked(mockSlackClient.getChannelUnread).mockResolvedValue({
246
- channel: mockChannelsWithUnread[0],
247
- messages: mockUnreadMessages,
248
- users: new Map([
249
- ['U123', 'john.doe'],
250
- ['U456', 'jane.smith']
251
- ])
252
- });
253
-
254
- await program.parseAsync(['node', 'slack-cli', 'unread', '--channel', 'general', '--count-only']);
255
-
256
- expect(mockSlackClient.getChannelUnread).toHaveBeenCalledWith('general');
257
- expect(mockConsole.logSpy).toHaveBeenCalledWith(chalk.bold(`#general: 5 unread messages`));
258
- expect(mockConsole.logSpy).not.toHaveBeenCalledWith(expect.stringContaining('Hello world'));
259
- expect(mockConsole.logSpy).not.toHaveBeenCalledWith(expect.stringContaining('Test message'));
260
- });
261
-
262
- it('should show count only in JSON format when both --count-only and --format json are specified', async () => {
263
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
264
- token: 'test-token',
265
- updatedAt: new Date().toISOString()
266
- });
267
- vi.mocked(mockSlackClient.getChannelUnread).mockResolvedValue({
268
- channel: mockChannelsWithUnread[0],
269
- messages: mockUnreadMessages,
270
- users: new Map([
271
- ['U123', 'john.doe'],
272
- ['U456', 'jane.smith']
273
- ])
274
- });
275
-
276
- await program.parseAsync(['node', 'slack-cli', 'unread', '--channel', 'general', '--count-only', '--format', 'json']);
277
-
278
- expect(mockSlackClient.getChannelUnread).toHaveBeenCalledWith('general');
279
-
280
- const logCall = mockConsole.logSpy.mock.calls[0][0];
281
- const parsed = JSON.parse(logCall);
282
-
283
- expect(parsed).toEqual({
284
- channel: '#general',
285
- channelId: 'C123',
286
- unreadCount: 5
287
- });
288
- });
289
- });
290
-
291
- describe('limit option', () => {
292
- it('should limit the number of channels displayed', async () => {
293
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
294
- token: 'test-token',
295
- updatedAt: new Date().toISOString()
296
- });
297
- vi.mocked(mockSlackClient.listUnreadChannels).mockResolvedValue(
298
- mockChannelsWithUnread.filter(ch => (ch.unread_count_display || 0) > 0)
299
- );
300
-
301
- await program.parseAsync(['node', 'slack-cli', 'unread', '--limit', '1']);
302
-
303
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('#general'));
304
- expect(mockConsole.logSpy).not.toHaveBeenCalledWith(expect.stringContaining('#random'));
305
- });
306
- });
307
-
308
- describe('error handling', () => {
309
- it('should display message when no unread messages found', async () => {
310
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
311
- token: 'test-token',
312
- updatedAt: new Date().toISOString()
313
- });
314
- vi.mocked(mockSlackClient.listUnreadChannels).mockResolvedValue([]);
315
-
316
- await program.parseAsync(['node', 'slack-cli', 'unread']);
317
-
318
- expect(mockConsole.logSpy).toHaveBeenCalledWith(chalk.green('✓ No unread messages'));
319
- });
320
-
321
- it('should handle channel not found error', async () => {
322
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
323
- token: 'test-token',
324
- updatedAt: new Date().toISOString()
325
- });
326
- vi.mocked(mockSlackClient.getChannelUnread).mockRejectedValue(
327
- new Error('channel_not_found')
328
- );
329
-
330
- await program.parseAsync(['node', 'slack-cli', 'unread', '--channel', 'nonexistent']);
331
-
332
- expect(mockConsole.errorSpy).toHaveBeenCalledWith(expect.stringContaining('Error:'), expect.any(String));
333
- expect(mockConsole.exitSpy).toHaveBeenCalledWith(1);
334
- });
335
-
336
- it('should handle missing configuration', async () => {
337
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue(null);
338
-
339
- await program.parseAsync(['node', 'slack-cli', 'unread']);
340
-
341
- expect(mockConsole.errorSpy).toHaveBeenCalledWith(expect.stringContaining('Error:'), expect.any(String));
342
- expect(mockConsole.exitSpy).toHaveBeenCalledWith(1);
343
- });
344
- });
345
-
346
- describe('profile option', () => {
347
- it('should use specified profile', async () => {
348
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
349
- token: 'work-token',
350
- updatedAt: new Date().toISOString()
351
- });
352
- vi.mocked(mockSlackClient.listUnreadChannels).mockResolvedValue([]);
353
-
354
- await program.parseAsync(['node', 'slack-cli', 'unread', '--profile', 'work']);
355
-
356
- expect(mockConfigManager.getConfig).toHaveBeenCalledWith('work');
357
- expect(SlackApiClient).toHaveBeenCalledWith('work-token');
358
- });
359
- });
360
-
361
- describe('rate limiting', () => {
362
- it('should handle rate limit errors gracefully', async () => {
363
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
364
- token: 'test-token',
365
- updatedAt: new Date().toISOString()
366
- });
367
-
368
- const rateLimitError = new Error('A rate limit was exceeded (url: conversations.info, retry-after: 10)');
369
- vi.mocked(mockSlackClient.listUnreadChannels).mockRejectedValue(rateLimitError);
370
-
371
- await program.parseAsync(['node', 'slack-cli', 'unread']);
372
-
373
- expect(mockConsole.errorSpy).toHaveBeenCalledWith(expect.stringContaining('Error:'), expect.stringContaining('rate limit'));
374
- expect(mockConsole.exitSpy).toHaveBeenCalledWith(1);
375
- });
376
-
377
- it('should not retry indefinitely on rate limit errors', async () => {
378
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
379
- token: 'test-token',
380
- updatedAt: new Date().toISOString()
381
- });
382
-
383
- const rateLimitError = new Error('A rate limit was exceeded (url: conversations.info, retry-after: 10)');
384
- vi.mocked(mockSlackClient.listUnreadChannels).mockRejectedValue(rateLimitError);
385
-
386
- await program.parseAsync(['node', 'slack-cli', 'unread']);
387
-
388
- expect(mockSlackClient.listUnreadChannels).toHaveBeenCalledTimes(1);
389
- });
390
- });
391
-
392
- describe('last_read timestamp handling', () => {
393
- it('should fetch messages after last_read timestamp even when unread_count is 0', async () => {
394
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
395
- token: 'test-token',
396
- updatedAt: new Date().toISOString()
397
- });
398
-
399
- const channelWithLastRead = {
400
- id: 'C08JFKGJPPE',
401
- name: 'dev_kiban_jira',
402
- is_channel: true,
403
- is_member: true,
404
- is_archived: false,
405
- unread_count: 0,
406
- unread_count_display: 0,
407
- last_read: '1750646034.663209',
408
- is_private: false,
409
- created: 1742353688
410
- };
411
-
412
- const unreadMessage = {
413
- ts: '1750646072.447069',
414
- user: 'U5F87BSGP',
415
- text: '@Suguru Sakashita / 阪下 駿 transitioned ES-4359 ArgumentError: \'発行者\' is not a valid field_name in Clip',
416
- type: 'message',
417
- };
418
-
419
- vi.mocked(mockSlackClient.getChannelUnread).mockResolvedValue({
420
- channel: { ...channelWithLastRead, unread_count: 1, unread_count_display: 1 },
421
- messages: [unreadMessage],
422
- users: new Map([['U5F87BSGP', 'jira-bot']])
423
- });
424
-
425
- await program.parseAsync(['node', 'slack-cli', 'unread', '--channel', 'dev_kiban_jira']);
426
-
427
- expect(mockSlackClient.getChannelUnread).toHaveBeenCalledWith('dev_kiban_jira');
428
- expect(mockConsole.logSpy).toHaveBeenCalledWith(chalk.bold('#dev_kiban_jira: 1 unread messages'));
429
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('transitioned ES-4359'));
430
- });
431
- });
432
-
433
- describe('mark-read functionality', () => {
434
- it('should mark messages as read when --mark-read is specified', async () => {
435
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
436
- token: 'test-token',
437
- updatedAt: new Date().toISOString()
438
- });
439
- vi.mocked(mockSlackClient.listUnreadChannels).mockResolvedValue(
440
- mockChannelsWithUnread.filter(ch => (ch.unread_count_display || 0) > 0)
441
- );
442
- vi.mocked(mockSlackClient.markAsRead).mockResolvedValue(undefined);
443
-
444
- await program.parseAsync(['node', 'slack-cli', 'unread', '--mark-read']);
445
-
446
- expect(mockSlackClient.listUnreadChannels).toHaveBeenCalled();
447
- expect(mockSlackClient.markAsRead).toHaveBeenCalledWith('C123');
448
- expect(mockSlackClient.markAsRead).toHaveBeenCalledWith('C456');
449
- expect(mockConsole.logSpy).toHaveBeenCalledWith(chalk.green('✓ Marked all messages as read'));
450
- });
451
-
452
- it('should mark messages as read for specific channel when --channel and --mark-read are specified', async () => {
453
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
454
- token: 'test-token',
455
- updatedAt: new Date().toISOString()
456
- });
457
- vi.mocked(mockSlackClient.getChannelUnread).mockResolvedValue({
458
- channel: mockChannelsWithUnread[0],
459
- messages: mockUnreadMessages,
460
- users: new Map([
461
- ['U123', 'john.doe'],
462
- ['U456', 'jane.smith']
463
- ])
464
- });
465
- vi.mocked(mockSlackClient.markAsRead).mockResolvedValue(undefined);
466
-
467
- await program.parseAsync(['node', 'slack-cli', 'unread', '--channel', 'general', '--mark-read']);
468
-
469
- expect(mockSlackClient.getChannelUnread).toHaveBeenCalledWith('general');
470
- expect(mockSlackClient.markAsRead).toHaveBeenCalledWith('C123');
471
- expect(mockConsole.logSpy).toHaveBeenCalledWith(chalk.green('✓ Marked messages in #general as read'));
472
- });
473
-
474
- it('should handle mark as read errors gracefully', async () => {
475
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
476
- token: 'test-token',
477
- updatedAt: new Date().toISOString()
478
- });
479
- vi.mocked(mockSlackClient.listUnreadChannels).mockResolvedValue(
480
- mockChannelsWithUnread.filter(ch => (ch.unread_count_display || 0) > 0)
481
- );
482
- vi.mocked(mockSlackClient.markAsRead).mockRejectedValue(
483
- new Error('channel_not_found')
484
- );
485
-
486
- await program.parseAsync(['node', 'slack-cli', 'unread', '--mark-read']);
487
-
488
- expect(mockConsole.errorSpy).toHaveBeenCalledWith(expect.stringContaining('Error:'), expect.any(String));
489
- expect(mockConsole.exitSpy).toHaveBeenCalledWith(1);
490
- });
491
- });
492
- });
@@ -1,40 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { execSync } from 'child_process';
3
- import { readFileSync } from 'fs';
4
- import { join } from 'path';
5
-
6
- describe('slack-cli version', () => {
7
- it('should display the correct version from package.json', { timeout: 20000 }, () => {
8
- // Read the expected version from package.json
9
- const packageJson = JSON.parse(
10
- readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')
11
- );
12
- const expectedVersion = packageJson.version;
13
-
14
- // Execute the CLI command to get version using ts-node
15
- const output = execSync('npx ts-node src/index.ts --version', {
16
- encoding: 'utf-8',
17
- cwd: join(__dirname, '..')
18
- }).trim();
19
-
20
- // The output should contain the version from package.json
21
- expect(output).toBe(expectedVersion);
22
- });
23
-
24
- it('should display version with -V flag', { timeout: 20000 }, () => {
25
- // Read the expected version from package.json
26
- const packageJson = JSON.parse(
27
- readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')
28
- );
29
- const expectedVersion = packageJson.version;
30
-
31
- // Execute the CLI command with -V flag using ts-node
32
- const output = execSync('npx ts-node src/index.ts -V', {
33
- encoding: 'utf-8',
34
- cwd: join(__dirname, '..')
35
- }).trim();
36
-
37
- // The output should contain the version from package.json
38
- expect(output).toBe(expectedVersion);
39
- });
40
- });
@@ -1,28 +0,0 @@
1
- import { vi } from 'vitest';
2
- import { Command } from 'commander';
3
-
4
- export interface MockConsole {
5
- logSpy: any;
6
- errorSpy: any;
7
- exitSpy: any;
8
- }
9
-
10
- export function setupMockConsole(): MockConsole {
11
- const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
12
- const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
13
- const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
14
-
15
- return { logSpy, errorSpy, exitSpy };
16
- }
17
-
18
- export function createTestProgram(): Command {
19
- const program = new Command();
20
- program.exitOverride((err) => {
21
- throw new Error('process.exit');
22
- });
23
- return program;
24
- }
25
-
26
- export function restoreMocks(): void {
27
- vi.restoreAllMocks();
28
- }