@urugus/slack-cli 0.2.9 → 0.2.11
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 +60 -14
- package/README.md +71 -28
- package/dist/commands/scheduled.d.ts +3 -0
- package/dist/commands/scheduled.d.ts.map +1 -0
- package/dist/commands/scheduled.js +55 -0
- package/dist/commands/scheduled.js.map +1 -0
- package/dist/commands/send.d.ts.map +1 -1
- package/dist/commands/send.js +16 -2
- package/dist/commands/send.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/types/commands.d.ts +8 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/utils/channel-resolver.d.ts.map +1 -1
- package/dist/utils/channel-resolver.js +1 -3
- package/dist/utils/channel-resolver.js.map +1 -1
- package/dist/utils/config.d.ts +10 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +94 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/constants.d.ts +5 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +5 -0
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/formatters/output-formatter.d.ts +7 -0
- package/dist/utils/formatters/output-formatter.d.ts.map +1 -0
- package/dist/utils/formatters/output-formatter.js +7 -0
- package/dist/utils/formatters/output-formatter.js.map +1 -0
- package/dist/utils/profile-config-refactored.d.ts +20 -0
- package/dist/utils/profile-config-refactored.d.ts.map +1 -0
- package/dist/utils/profile-config-refactored.js +174 -0
- package/dist/utils/profile-config-refactored.js.map +1 -0
- package/dist/utils/schedule-utils.d.ts +3 -0
- package/dist/utils/schedule-utils.d.ts.map +1 -0
- package/dist/utils/schedule-utils.js +34 -0
- package/dist/utils/schedule-utils.js.map +1 -0
- package/dist/utils/slack-api-client.d.ts +10 -1
- package/dist/utils/slack-api-client.d.ts.map +1 -1
- package/dist/utils/slack-api-client.js +6 -0
- package/dist/utils/slack-api-client.js.map +1 -1
- package/dist/utils/slack-operations/message-operations.d.ts +4 -2
- package/dist/utils/slack-operations/message-operations.d.ts.map +1 -1
- package/dist/utils/slack-operations/message-operations.js +28 -0
- package/dist/utils/slack-operations/message-operations.js.map +1 -1
- package/dist/utils/validators.d.ts +4 -0
- package/dist/utils/validators.d.ts.map +1 -1
- package/dist/utils/validators.js +31 -0
- package/dist/utils/validators.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/scheduled.ts +71 -0
- package/src/commands/send.ts +21 -3
- package/src/index.ts +2 -0
- package/src/types/commands.ts +9 -0
- package/src/utils/channel-resolver.ts +1 -5
- package/src/utils/constants.ts +7 -0
- package/src/utils/schedule-utils.ts +41 -0
- package/src/utils/slack-api-client.ts +22 -1
- package/src/utils/slack-operations/message-operations.ts +55 -2
- package/src/utils/validators.ts +38 -0
- package/tests/commands/scheduled.test.ts +131 -0
- package/tests/commands/send.test.ts +235 -44
- package/tests/utils/channel-resolver.test.ts +25 -21
- package/tests/utils/schedule-utils.test.ts +63 -0
- package/tests/utils/slack-api-client.test.ts +81 -46
- package/tests/utils/slack-operations/message-operations.test.ts +38 -1
|
@@ -18,13 +18,13 @@ describe('send command', () => {
|
|
|
18
18
|
|
|
19
19
|
beforeEach(() => {
|
|
20
20
|
vi.clearAllMocks();
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
mockConfigManager = new ProfileConfigManager();
|
|
23
23
|
vi.mocked(ProfileConfigManager).mockReturnValue(mockConfigManager);
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
mockSlackClient = new SlackApiClient('test-token');
|
|
26
26
|
vi.mocked(SlackApiClient).mockReturnValue(mockSlackClient);
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
mockConsole = setupMockConsole();
|
|
29
29
|
program = createTestProgram();
|
|
30
30
|
program.addCommand(setupSendCommand());
|
|
@@ -38,30 +38,42 @@ describe('send command', () => {
|
|
|
38
38
|
it('should send a message to specified channel', async () => {
|
|
39
39
|
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
40
40
|
token: 'test-token',
|
|
41
|
-
updatedAt: new Date().toISOString()
|
|
41
|
+
updatedAt: new Date().toISOString(),
|
|
42
42
|
});
|
|
43
43
|
vi.mocked(mockSlackClient.sendMessage).mockResolvedValue({
|
|
44
44
|
ok: true,
|
|
45
|
-
ts: '1234567890.123456'
|
|
45
|
+
ts: '1234567890.123456',
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
await program.parseAsync(['node', 'slack-cli', 'send', '-c', 'general', '-m', 'Hello, World!']);
|
|
49
49
|
|
|
50
50
|
expect(mockSlackClient.sendMessage).toHaveBeenCalledWith('general', 'Hello, World!', undefined);
|
|
51
|
-
expect(mockConsole.logSpy).toHaveBeenCalledWith(
|
|
51
|
+
expect(mockConsole.logSpy).toHaveBeenCalledWith(
|
|
52
|
+
expect.stringContaining(SUCCESS_MESSAGES.MESSAGE_SENT('general'))
|
|
53
|
+
);
|
|
52
54
|
});
|
|
53
55
|
|
|
54
56
|
it('should use specified profile', async () => {
|
|
55
57
|
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
56
58
|
token: 'work-token',
|
|
57
|
-
updatedAt: new Date().toISOString()
|
|
59
|
+
updatedAt: new Date().toISOString(),
|
|
58
60
|
});
|
|
59
61
|
vi.mocked(mockSlackClient.sendMessage).mockResolvedValue({
|
|
60
62
|
ok: true,
|
|
61
|
-
ts: '1234567890.123456'
|
|
63
|
+
ts: '1234567890.123456',
|
|
62
64
|
});
|
|
63
65
|
|
|
64
|
-
await program.parseAsync([
|
|
66
|
+
await program.parseAsync([
|
|
67
|
+
'node',
|
|
68
|
+
'slack-cli',
|
|
69
|
+
'send',
|
|
70
|
+
'-c',
|
|
71
|
+
'general',
|
|
72
|
+
'-m',
|
|
73
|
+
'Hello',
|
|
74
|
+
'--profile',
|
|
75
|
+
'work',
|
|
76
|
+
]);
|
|
65
77
|
|
|
66
78
|
expect(mockConfigManager.getConfig).toHaveBeenCalledWith('work');
|
|
67
79
|
expect(SlackApiClient).toHaveBeenCalledWith('work-token');
|
|
@@ -74,11 +86,11 @@ describe('send command', () => {
|
|
|
74
86
|
vi.mocked(fs.readFile).mockResolvedValue(fileContent);
|
|
75
87
|
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
76
88
|
token: 'test-token',
|
|
77
|
-
updatedAt: new Date().toISOString()
|
|
89
|
+
updatedAt: new Date().toISOString(),
|
|
78
90
|
});
|
|
79
91
|
vi.mocked(mockSlackClient.sendMessage).mockResolvedValue({
|
|
80
92
|
ok: true,
|
|
81
|
-
ts: '1234567890.123456'
|
|
93
|
+
ts: '1234567890.123456',
|
|
82
94
|
});
|
|
83
95
|
|
|
84
96
|
await program.parseAsync(['node', 'slack-cli', 'send', '-c', 'general', '-f', 'message.txt']);
|
|
@@ -92,46 +104,80 @@ describe('send command', () => {
|
|
|
92
104
|
it('should send a reply to a thread with --thread option', async () => {
|
|
93
105
|
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
94
106
|
token: 'test-token',
|
|
95
|
-
updatedAt: new Date().toISOString()
|
|
107
|
+
updatedAt: new Date().toISOString(),
|
|
96
108
|
});
|
|
97
109
|
vi.mocked(mockSlackClient.sendMessage).mockResolvedValue({
|
|
98
110
|
ok: true,
|
|
99
|
-
ts: '1234567890.123456'
|
|
111
|
+
ts: '1234567890.123456',
|
|
100
112
|
});
|
|
101
113
|
|
|
102
|
-
await program.parseAsync([
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
114
|
+
await program.parseAsync([
|
|
115
|
+
'node',
|
|
116
|
+
'slack-cli',
|
|
117
|
+
'send',
|
|
118
|
+
'-c',
|
|
119
|
+
'general',
|
|
120
|
+
'-m',
|
|
121
|
+
'Reply to thread',
|
|
122
|
+
'--thread',
|
|
123
|
+
'1719207629.000100',
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
expect(mockSlackClient.sendMessage).toHaveBeenCalledWith(
|
|
127
|
+
'general',
|
|
128
|
+
'Reply to thread',
|
|
129
|
+
'1719207629.000100'
|
|
130
|
+
);
|
|
131
|
+
expect(mockConsole.logSpy).toHaveBeenCalledWith(
|
|
132
|
+
expect.stringContaining(SUCCESS_MESSAGES.MESSAGE_SENT('general'))
|
|
133
|
+
);
|
|
106
134
|
});
|
|
107
135
|
|
|
108
136
|
it('should send a reply to a thread with -t option', async () => {
|
|
109
137
|
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
110
138
|
token: 'test-token',
|
|
111
|
-
updatedAt: new Date().toISOString()
|
|
139
|
+
updatedAt: new Date().toISOString(),
|
|
112
140
|
});
|
|
113
141
|
vi.mocked(mockSlackClient.sendMessage).mockResolvedValue({
|
|
114
142
|
ok: true,
|
|
115
|
-
ts: '1234567890.123456'
|
|
143
|
+
ts: '1234567890.123456',
|
|
116
144
|
});
|
|
117
145
|
|
|
118
|
-
await program.parseAsync([
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
146
|
+
await program.parseAsync([
|
|
147
|
+
'node',
|
|
148
|
+
'slack-cli',
|
|
149
|
+
'send',
|
|
150
|
+
'-c',
|
|
151
|
+
'general',
|
|
152
|
+
'-m',
|
|
153
|
+
'Reply to thread',
|
|
154
|
+
'-t',
|
|
155
|
+
'1719207629.000100',
|
|
156
|
+
]);
|
|
157
|
+
|
|
158
|
+
expect(mockSlackClient.sendMessage).toHaveBeenCalledWith(
|
|
159
|
+
'general',
|
|
160
|
+
'Reply to thread',
|
|
161
|
+
'1719207629.000100'
|
|
162
|
+
);
|
|
163
|
+
expect(mockConsole.logSpy).toHaveBeenCalledWith(
|
|
164
|
+
expect.stringContaining(SUCCESS_MESSAGES.MESSAGE_SENT('general'))
|
|
165
|
+
);
|
|
122
166
|
});
|
|
123
167
|
|
|
124
168
|
it('should validate thread timestamp format', async () => {
|
|
125
169
|
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
126
170
|
token: 'test-token',
|
|
127
|
-
updatedAt: new Date().toISOString()
|
|
171
|
+
updatedAt: new Date().toISOString(),
|
|
128
172
|
});
|
|
129
173
|
|
|
130
174
|
const sendCommand = setupSendCommand();
|
|
131
175
|
sendCommand.exitOverride();
|
|
132
176
|
|
|
133
177
|
await expect(
|
|
134
|
-
sendCommand.parseAsync(['-c', 'general', '-m', 'Reply', '-t', 'invalid-timestamp'], {
|
|
178
|
+
sendCommand.parseAsync(['-c', 'general', '-m', 'Reply', '-t', 'invalid-timestamp'], {
|
|
179
|
+
from: 'user',
|
|
180
|
+
})
|
|
135
181
|
).rejects.toThrow(ERROR_MESSAGES.INVALID_THREAD_TIMESTAMP);
|
|
136
182
|
});
|
|
137
183
|
|
|
@@ -140,18 +186,107 @@ describe('send command', () => {
|
|
|
140
186
|
vi.mocked(fs.readFile).mockResolvedValue(fileContent);
|
|
141
187
|
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
142
188
|
token: 'test-token',
|
|
143
|
-
updatedAt: new Date().toISOString()
|
|
189
|
+
updatedAt: new Date().toISOString(),
|
|
144
190
|
});
|
|
145
191
|
vi.mocked(mockSlackClient.sendMessage).mockResolvedValue({
|
|
146
192
|
ok: true,
|
|
147
|
-
ts: '1234567890.123456'
|
|
193
|
+
ts: '1234567890.123456',
|
|
148
194
|
});
|
|
149
195
|
|
|
150
|
-
await program.parseAsync([
|
|
196
|
+
await program.parseAsync([
|
|
197
|
+
'node',
|
|
198
|
+
'slack-cli',
|
|
199
|
+
'send',
|
|
200
|
+
'-c',
|
|
201
|
+
'general',
|
|
202
|
+
'-f',
|
|
203
|
+
'reply.txt',
|
|
204
|
+
'-t',
|
|
205
|
+
'1719207629.000100',
|
|
206
|
+
]);
|
|
151
207
|
|
|
152
208
|
expect(fs.readFile).toHaveBeenCalledWith('reply.txt', 'utf-8');
|
|
153
|
-
expect(mockSlackClient.sendMessage).toHaveBeenCalledWith(
|
|
154
|
-
|
|
209
|
+
expect(mockSlackClient.sendMessage).toHaveBeenCalledWith(
|
|
210
|
+
'general',
|
|
211
|
+
fileContent,
|
|
212
|
+
'1719207629.000100'
|
|
213
|
+
);
|
|
214
|
+
expect(mockConsole.logSpy).toHaveBeenCalledWith(
|
|
215
|
+
expect.stringContaining(SUCCESS_MESSAGES.MESSAGE_SENT('general'))
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('schedule message', () => {
|
|
221
|
+
it('should schedule message with --at', async () => {
|
|
222
|
+
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
223
|
+
token: 'test-token',
|
|
224
|
+
updatedAt: new Date().toISOString(),
|
|
225
|
+
});
|
|
226
|
+
vi.mocked(mockSlackClient.scheduleMessage).mockResolvedValue({
|
|
227
|
+
ok: true,
|
|
228
|
+
scheduled_message_id: 'Q1298393284',
|
|
229
|
+
post_at: 2051258400,
|
|
230
|
+
} as any);
|
|
231
|
+
|
|
232
|
+
await program.parseAsync([
|
|
233
|
+
'node',
|
|
234
|
+
'slack-cli',
|
|
235
|
+
'send',
|
|
236
|
+
'-c',
|
|
237
|
+
'general',
|
|
238
|
+
'-m',
|
|
239
|
+
'Future message',
|
|
240
|
+
'--at',
|
|
241
|
+
'2035-01-01T10:00:00Z',
|
|
242
|
+
]);
|
|
243
|
+
|
|
244
|
+
expect(mockSlackClient.scheduleMessage).toHaveBeenCalledWith(
|
|
245
|
+
'general',
|
|
246
|
+
'Future message',
|
|
247
|
+
2051258400,
|
|
248
|
+
undefined
|
|
249
|
+
);
|
|
250
|
+
expect(mockSlackClient.sendMessage).not.toHaveBeenCalled();
|
|
251
|
+
expect(mockConsole.logSpy).toHaveBeenCalledWith(
|
|
252
|
+
expect.stringContaining('Message scheduled to #general')
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should schedule message with --after', async () => {
|
|
257
|
+
vi.useFakeTimers();
|
|
258
|
+
vi.setSystemTime(new Date('2026-02-12T00:00:00Z'));
|
|
259
|
+
|
|
260
|
+
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
261
|
+
token: 'test-token',
|
|
262
|
+
updatedAt: new Date().toISOString(),
|
|
263
|
+
});
|
|
264
|
+
vi.mocked(mockSlackClient.scheduleMessage).mockResolvedValue({
|
|
265
|
+
ok: true,
|
|
266
|
+
scheduled_message_id: 'Q1298393284',
|
|
267
|
+
post_at: 1770868800,
|
|
268
|
+
} as any);
|
|
269
|
+
|
|
270
|
+
await program.parseAsync([
|
|
271
|
+
'node',
|
|
272
|
+
'slack-cli',
|
|
273
|
+
'send',
|
|
274
|
+
'-c',
|
|
275
|
+
'general',
|
|
276
|
+
'-m',
|
|
277
|
+
'Future message',
|
|
278
|
+
'--after',
|
|
279
|
+
'10',
|
|
280
|
+
]);
|
|
281
|
+
|
|
282
|
+
expect(mockSlackClient.scheduleMessage).toHaveBeenCalledWith(
|
|
283
|
+
'general',
|
|
284
|
+
'Future message',
|
|
285
|
+
1770855000,
|
|
286
|
+
undefined
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
vi.useRealTimers();
|
|
155
290
|
});
|
|
156
291
|
});
|
|
157
292
|
|
|
@@ -160,9 +295,9 @@ describe('send command', () => {
|
|
|
160
295
|
const sendCommand = setupSendCommand();
|
|
161
296
|
sendCommand.exitOverride();
|
|
162
297
|
|
|
163
|
-
await expect(
|
|
164
|
-
|
|
165
|
-
)
|
|
298
|
+
await expect(sendCommand.parseAsync(['-c', 'general'], { from: 'user' })).rejects.toThrow(
|
|
299
|
+
`Error: ${ERROR_MESSAGES.NO_MESSAGE_OR_FILE}`
|
|
300
|
+
);
|
|
166
301
|
});
|
|
167
302
|
|
|
168
303
|
it('should fail when both message and file are provided', async () => {
|
|
@@ -170,17 +305,64 @@ describe('send command', () => {
|
|
|
170
305
|
sendCommand.exitOverride();
|
|
171
306
|
|
|
172
307
|
await expect(
|
|
173
|
-
sendCommand.parseAsync(['-c', 'general', '-m', 'Hello', '-f', 'file.txt'], {
|
|
308
|
+
sendCommand.parseAsync(['-c', 'general', '-m', 'Hello', '-f', 'file.txt'], {
|
|
309
|
+
from: 'user',
|
|
310
|
+
})
|
|
174
311
|
).rejects.toThrow(`Error: ${ERROR_MESSAGES.BOTH_MESSAGE_AND_FILE}`);
|
|
175
312
|
});
|
|
176
313
|
|
|
177
|
-
it('should fail when
|
|
314
|
+
it('should fail when both --at and --after are provided', async () => {
|
|
315
|
+
const sendCommand = setupSendCommand();
|
|
316
|
+
sendCommand.exitOverride();
|
|
317
|
+
|
|
318
|
+
await expect(
|
|
319
|
+
sendCommand.parseAsync(
|
|
320
|
+
['-c', 'general', '-m', 'Hello', '--at', '2030-01-01T10:00:00Z', '--after', '10'],
|
|
321
|
+
{
|
|
322
|
+
from: 'user',
|
|
323
|
+
}
|
|
324
|
+
)
|
|
325
|
+
).rejects.toThrow(`Error: ${ERROR_MESSAGES.BOTH_SCHEDULE_OPTIONS}`);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('should fail with invalid --at', async () => {
|
|
329
|
+
const sendCommand = setupSendCommand();
|
|
330
|
+
sendCommand.exitOverride();
|
|
331
|
+
|
|
332
|
+
await expect(
|
|
333
|
+
sendCommand.parseAsync(['-c', 'general', '-m', 'Hello', '--at', 'invalid-date'], {
|
|
334
|
+
from: 'user',
|
|
335
|
+
})
|
|
336
|
+
).rejects.toThrow(`Error: ${ERROR_MESSAGES.INVALID_SCHEDULE_AT}`);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('should fail with past --at', async () => {
|
|
178
340
|
const sendCommand = setupSendCommand();
|
|
179
341
|
sendCommand.exitOverride();
|
|
180
|
-
|
|
342
|
+
|
|
181
343
|
await expect(
|
|
182
|
-
sendCommand.parseAsync(['-m', 'Hello'
|
|
183
|
-
|
|
344
|
+
sendCommand.parseAsync(['-c', 'general', '-m', 'Hello', '--at', '1'], {
|
|
345
|
+
from: 'user',
|
|
346
|
+
})
|
|
347
|
+
).rejects.toThrow(`Error: ${ERROR_MESSAGES.SCHEDULE_TIME_IN_PAST}`);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should fail with invalid --after', async () => {
|
|
351
|
+
const sendCommand = setupSendCommand();
|
|
352
|
+
sendCommand.exitOverride();
|
|
353
|
+
|
|
354
|
+
await expect(
|
|
355
|
+
sendCommand.parseAsync(['-c', 'general', '-m', 'Hello', '--after', '0'], {
|
|
356
|
+
from: 'user',
|
|
357
|
+
})
|
|
358
|
+
).rejects.toThrow(`Error: ${ERROR_MESSAGES.INVALID_SCHEDULE_AFTER}`);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should fail when no channel is provided', async () => {
|
|
362
|
+
const sendCommand = setupSendCommand();
|
|
363
|
+
sendCommand.exitOverride();
|
|
364
|
+
|
|
365
|
+
await expect(sendCommand.parseAsync(['-m', 'Hello'], { from: 'user' })).rejects.toThrow();
|
|
184
366
|
});
|
|
185
367
|
});
|
|
186
368
|
|
|
@@ -190,20 +372,26 @@ describe('send command', () => {
|
|
|
190
372
|
|
|
191
373
|
await program.parseAsync(['node', 'slack-cli', 'send', '-c', 'general', '-m', 'Hello']);
|
|
192
374
|
|
|
193
|
-
expect(mockConsole.errorSpy).toHaveBeenCalledWith(
|
|
375
|
+
expect(mockConsole.errorSpy).toHaveBeenCalledWith(
|
|
376
|
+
expect.stringContaining('Error:'),
|
|
377
|
+
expect.any(String)
|
|
378
|
+
);
|
|
194
379
|
expect(mockConsole.exitSpy).toHaveBeenCalledWith(1);
|
|
195
380
|
});
|
|
196
381
|
|
|
197
382
|
it('should handle Slack API errors', async () => {
|
|
198
383
|
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
199
384
|
token: 'test-token',
|
|
200
|
-
updatedAt: new Date().toISOString()
|
|
385
|
+
updatedAt: new Date().toISOString(),
|
|
201
386
|
});
|
|
202
387
|
vi.mocked(mockSlackClient.sendMessage).mockRejectedValue(new Error('channel_not_found'));
|
|
203
388
|
|
|
204
389
|
await program.parseAsync(['node', 'slack-cli', 'send', '-c', 'nonexistent', '-m', 'Hello']);
|
|
205
390
|
|
|
206
|
-
expect(mockConsole.errorSpy).toHaveBeenCalledWith(
|
|
391
|
+
expect(mockConsole.errorSpy).toHaveBeenCalledWith(
|
|
392
|
+
expect.stringContaining('Error:'),
|
|
393
|
+
expect.any(String)
|
|
394
|
+
);
|
|
207
395
|
expect(mockConsole.exitSpy).toHaveBeenCalledWith(1);
|
|
208
396
|
});
|
|
209
397
|
|
|
@@ -211,13 +399,16 @@ describe('send command', () => {
|
|
|
211
399
|
vi.mocked(fs.readFile).mockRejectedValue(new Error('ENOENT: no such file'));
|
|
212
400
|
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
213
401
|
token: 'test-token',
|
|
214
|
-
updatedAt: new Date().toISOString()
|
|
402
|
+
updatedAt: new Date().toISOString(),
|
|
215
403
|
});
|
|
216
404
|
|
|
217
405
|
await program.parseAsync(['node', 'slack-cli', 'send', '-c', 'general', '-f', 'nonexistent.txt']);
|
|
218
406
|
|
|
219
|
-
expect(mockConsole.errorSpy).toHaveBeenCalledWith(
|
|
407
|
+
expect(mockConsole.errorSpy).toHaveBeenCalledWith(
|
|
408
|
+
expect.stringContaining('Error:'),
|
|
409
|
+
expect.any(String)
|
|
410
|
+
);
|
|
220
411
|
expect(mockConsole.exitSpy).toHaveBeenCalledWith(1);
|
|
221
412
|
});
|
|
222
413
|
});
|
|
223
|
-
});
|
|
414
|
+
});
|
|
@@ -10,21 +10,21 @@ describe('ChannelResolver', () => {
|
|
|
10
10
|
resolver = new ChannelResolver();
|
|
11
11
|
mockChannels = [
|
|
12
12
|
{
|
|
13
|
-
id: '
|
|
13
|
+
id: 'C1234567890',
|
|
14
14
|
name: 'general',
|
|
15
15
|
is_private: false,
|
|
16
16
|
created: 1234567890,
|
|
17
17
|
is_member: true,
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
|
-
id: '
|
|
20
|
+
id: 'C0987654321',
|
|
21
21
|
name: 'random',
|
|
22
22
|
is_private: false,
|
|
23
23
|
created: 1234567890,
|
|
24
24
|
is_member: true,
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
|
-
id: '
|
|
27
|
+
id: 'C1111111111',
|
|
28
28
|
name: 'dev-team',
|
|
29
29
|
is_private: false,
|
|
30
30
|
created: 1234567890,
|
|
@@ -32,7 +32,7 @@ describe('ChannelResolver', () => {
|
|
|
32
32
|
name_normalized: 'dev-team',
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
|
-
id: '
|
|
35
|
+
id: 'G1234567890',
|
|
36
36
|
name: 'private-channel',
|
|
37
37
|
is_private: true,
|
|
38
38
|
created: 1234567890,
|
|
@@ -42,21 +42,18 @@ describe('ChannelResolver', () => {
|
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
describe('isChannelId', () => {
|
|
45
|
-
it('should identify channel IDs
|
|
46
|
-
expect(resolver.isChannelId('
|
|
45
|
+
it('should identify channel IDs with valid format', () => {
|
|
46
|
+
expect(resolver.isChannelId('C1234567890')).toBe(true);
|
|
47
|
+
expect(resolver.isChannelId('D1234567890')).toBe(true);
|
|
48
|
+
expect(resolver.isChannelId('G1234567890')).toBe(true);
|
|
47
49
|
});
|
|
48
50
|
|
|
49
|
-
it('should
|
|
50
|
-
expect(resolver.isChannelId('D123456')).toBe(true);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should identify group IDs starting with G', () => {
|
|
54
|
-
expect(resolver.isChannelId('G123456')).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should return false for channel names', () => {
|
|
51
|
+
it('should return false for channel names or malformed IDs', () => {
|
|
58
52
|
expect(resolver.isChannelId('general')).toBe(false);
|
|
59
53
|
expect(resolver.isChannelId('#general')).toBe(false);
|
|
54
|
+
expect(resolver.isChannelId('General')).toBe(false);
|
|
55
|
+
expect(resolver.isChannelId('Dev')).toBe(false);
|
|
56
|
+
expect(resolver.isChannelId('C123')).toBe(false);
|
|
60
57
|
});
|
|
61
58
|
});
|
|
62
59
|
|
|
@@ -96,8 +93,8 @@ describe('ChannelResolver', () => {
|
|
|
96
93
|
it('should limit results to specified count', () => {
|
|
97
94
|
const manyChannels = [
|
|
98
95
|
...mockChannels,
|
|
99
|
-
{ id: '
|
|
100
|
-
{ id: '
|
|
96
|
+
{ id: 'C9999999999', name: 'general-2', is_private: false, created: 0 },
|
|
97
|
+
{ id: 'C8888888888', name: 'general-3', is_private: false, created: 0 },
|
|
101
98
|
];
|
|
102
99
|
const result = resolver.getSimilarChannels('general', manyChannels, 2);
|
|
103
100
|
expect(result).toHaveLength(2);
|
|
@@ -128,15 +125,22 @@ describe('ChannelResolver', () => {
|
|
|
128
125
|
describe('resolveChannelId', () => {
|
|
129
126
|
it('should return ID directly if already an ID', async () => {
|
|
130
127
|
const getChannelsFn = vi.fn();
|
|
131
|
-
const result = await resolver.resolveChannelId('
|
|
132
|
-
expect(result).toBe('
|
|
128
|
+
const result = await resolver.resolveChannelId('C1234567890', getChannelsFn);
|
|
129
|
+
expect(result).toBe('C1234567890');
|
|
133
130
|
expect(getChannelsFn).not.toHaveBeenCalled();
|
|
134
131
|
});
|
|
135
132
|
|
|
136
133
|
it('should resolve channel name to ID', async () => {
|
|
137
134
|
const getChannelsFn = vi.fn().mockResolvedValue(mockChannels);
|
|
138
135
|
const result = await resolver.resolveChannelId('general', getChannelsFn);
|
|
139
|
-
expect(result).toBe('
|
|
136
|
+
expect(result).toBe('C1234567890');
|
|
137
|
+
expect(getChannelsFn).toHaveBeenCalled();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should resolve mixed-case channel names to ID', async () => {
|
|
141
|
+
const getChannelsFn = vi.fn().mockResolvedValue(mockChannels);
|
|
142
|
+
const result = await resolver.resolveChannelId('General', getChannelsFn);
|
|
143
|
+
expect(result).toBe('C1234567890');
|
|
140
144
|
expect(getChannelsFn).toHaveBeenCalled();
|
|
141
145
|
});
|
|
142
146
|
|
|
@@ -154,4 +158,4 @@ describe('ChannelResolver', () => {
|
|
|
154
158
|
);
|
|
155
159
|
});
|
|
156
160
|
});
|
|
157
|
-
});
|
|
161
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { parseScheduledTimestamp, resolvePostAt } from '../../src/utils/schedule-utils';
|
|
3
|
+
import { optionValidators } from '../../src/utils/validators';
|
|
4
|
+
|
|
5
|
+
describe('schedule utils', () => {
|
|
6
|
+
describe('parseScheduledTimestamp', () => {
|
|
7
|
+
it('parses unix timestamp seconds', () => {
|
|
8
|
+
expect(parseScheduledTimestamp('1770855000')).toBe(1770855000);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('parses ISO date string', () => {
|
|
12
|
+
expect(parseScheduledTimestamp('2026-02-12T00:10:00Z')).toBe(1770855000);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('returns null for invalid input', () => {
|
|
16
|
+
expect(parseScheduledTimestamp('invalid')).toBeNull();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('resolvePostAt', () => {
|
|
21
|
+
it('returns parsed timestamp for --at', () => {
|
|
22
|
+
expect(resolvePostAt('1770855000', undefined)).toBe(1770855000);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('returns now + minutes for --after', () => {
|
|
26
|
+
expect(resolvePostAt(undefined, '10', Date.parse('2026-02-12T00:00:00Z'))).toBe(1770855000);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('returns null when invalid --after is provided', () => {
|
|
30
|
+
expect(resolvePostAt(undefined, '0')).toBeNull();
|
|
31
|
+
expect(resolvePostAt(undefined, '1.5')).toBeNull();
|
|
32
|
+
expect(resolvePostAt(undefined, '10minutes')).toBeNull();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('optionValidators.scheduleTiming', () => {
|
|
37
|
+
it('rejects both --at and --after', () => {
|
|
38
|
+
expect(optionValidators.scheduleTiming({ at: '1770855000', after: '10' })).toBe(
|
|
39
|
+
'Cannot use both --at and --after'
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('rejects past --at timestamp', () => {
|
|
44
|
+
vi.useFakeTimers();
|
|
45
|
+
vi.setSystemTime(new Date('2026-02-12T00:00:00Z'));
|
|
46
|
+
|
|
47
|
+
expect(optionValidators.scheduleTiming({ at: '1770854300' })).toBe(
|
|
48
|
+
'Schedule time must be in the future'
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
vi.useRealTimers();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('rejects non-integer --after values', () => {
|
|
55
|
+
expect(optionValidators.scheduleTiming({ after: '1.5' })).toBe(
|
|
56
|
+
'--after must be a positive integer (minutes)'
|
|
57
|
+
);
|
|
58
|
+
expect(optionValidators.scheduleTiming({ after: '10minutes' })).toBe(
|
|
59
|
+
'--after must be a positive integer (minutes)'
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|