@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
package/src/commands/send.ts
CHANGED
|
@@ -8,19 +8,26 @@ import { SendOptions } from '../types/commands';
|
|
|
8
8
|
import { extractErrorMessage } from '../utils/error-utils';
|
|
9
9
|
import { parseProfile } from '../utils/option-parsers';
|
|
10
10
|
import { createValidationHook, optionValidators } from '../utils/validators';
|
|
11
|
+
import { resolvePostAt } from '../utils/schedule-utils';
|
|
11
12
|
import * as fs from 'fs/promises';
|
|
12
13
|
|
|
13
14
|
export function setupSendCommand(): Command {
|
|
14
15
|
const sendCommand = new Command('send')
|
|
15
|
-
.description('Send a message to a Slack channel')
|
|
16
|
+
.description('Send or schedule a message to a Slack channel')
|
|
16
17
|
.requiredOption('-c, --channel <channel>', 'Target channel name or ID')
|
|
17
18
|
.option('-m, --message <message>', 'Message to send')
|
|
18
19
|
.option('-f, --file <file>', 'File containing message content')
|
|
19
20
|
.option('-t, --thread <thread>', 'Thread timestamp to reply to')
|
|
21
|
+
.option('--at <time>', 'Schedule time (Unix timestamp in seconds or ISO 8601)')
|
|
22
|
+
.option('--after <minutes>', 'Schedule message after N minutes')
|
|
20
23
|
.option('--profile <profile>', 'Use specific workspace profile')
|
|
21
24
|
.hook(
|
|
22
25
|
'preAction',
|
|
23
|
-
createValidationHook([
|
|
26
|
+
createValidationHook([
|
|
27
|
+
optionValidators.messageOrFile,
|
|
28
|
+
optionValidators.threadTimestamp,
|
|
29
|
+
optionValidators.scheduleTiming,
|
|
30
|
+
])
|
|
24
31
|
)
|
|
25
32
|
.action(
|
|
26
33
|
wrapCommand(async (options: SendOptions) => {
|
|
@@ -38,11 +45,22 @@ export function setupSendCommand(): Command {
|
|
|
38
45
|
messageContent = options.message!; // This is safe because of preAction validation
|
|
39
46
|
}
|
|
40
47
|
|
|
48
|
+
const postAt = resolvePostAt(options.at, options.after);
|
|
49
|
+
|
|
41
50
|
// Send message
|
|
42
51
|
const profile = parseProfile(options.profile);
|
|
43
52
|
const client = await createSlackClient(profile);
|
|
44
|
-
await client.sendMessage(options.channel, messageContent, options.thread);
|
|
45
53
|
|
|
54
|
+
if (postAt !== null) {
|
|
55
|
+
await client.scheduleMessage(options.channel, messageContent, postAt, options.thread);
|
|
56
|
+
const postAtIso = new Date(postAt * 1000).toISOString();
|
|
57
|
+
console.log(
|
|
58
|
+
chalk.green(`✓ ${SUCCESS_MESSAGES.MESSAGE_SCHEDULED(options.channel, postAtIso)}`)
|
|
59
|
+
);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await client.sendMessage(options.channel, messageContent, options.thread);
|
|
46
64
|
console.log(chalk.green(`✓ ${SUCCESS_MESSAGES.MESSAGE_SENT(options.channel)}`));
|
|
47
65
|
})
|
|
48
66
|
);
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { setupSendCommand } from './commands/send';
|
|
|
5
5
|
import { setupChannelsCommand } from './commands/channels';
|
|
6
6
|
import { setupHistoryCommand } from './commands/history';
|
|
7
7
|
import { setupUnreadCommand } from './commands/unread';
|
|
8
|
+
import { setupScheduledCommand } from './commands/scheduled';
|
|
8
9
|
import { readFileSync } from 'fs';
|
|
9
10
|
import { join } from 'path';
|
|
10
11
|
|
|
@@ -21,5 +22,6 @@ program.addCommand(setupSendCommand());
|
|
|
21
22
|
program.addCommand(setupChannelsCommand());
|
|
22
23
|
program.addCommand(setupHistoryCommand());
|
|
23
24
|
program.addCommand(setupUnreadCommand());
|
|
25
|
+
program.addCommand(setupScheduledCommand());
|
|
24
26
|
|
|
25
27
|
program.parse();
|
package/src/types/commands.ts
CHANGED
|
@@ -20,6 +20,15 @@ export interface SendOptions {
|
|
|
20
20
|
message?: string;
|
|
21
21
|
file?: string;
|
|
22
22
|
thread?: string;
|
|
23
|
+
at?: string;
|
|
24
|
+
after?: string;
|
|
25
|
+
profile?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ScheduledOptions {
|
|
29
|
+
channel?: string;
|
|
30
|
+
limit?: string;
|
|
31
|
+
format?: 'table' | 'simple' | 'json';
|
|
23
32
|
profile?: string;
|
|
24
33
|
}
|
|
25
34
|
|
|
@@ -7,11 +7,7 @@ export class ChannelResolver {
|
|
|
7
7
|
* Check if the given string is a channel ID
|
|
8
8
|
*/
|
|
9
9
|
isChannelId(channelNameOrId: string): boolean {
|
|
10
|
-
return (
|
|
11
|
-
channelNameOrId.startsWith('C') ||
|
|
12
|
-
channelNameOrId.startsWith('D') ||
|
|
13
|
-
channelNameOrId.startsWith('G')
|
|
14
|
-
);
|
|
10
|
+
return /^[CDG][A-Z0-9]{8,}$/.test(channelNameOrId);
|
|
15
11
|
}
|
|
16
12
|
|
|
17
13
|
/**
|
package/src/utils/constants.ts
CHANGED
|
@@ -14,6 +14,11 @@ export const ERROR_MESSAGES = {
|
|
|
14
14
|
NO_MESSAGE_OR_FILE: 'You must specify either --message or --file',
|
|
15
15
|
BOTH_MESSAGE_AND_FILE: 'Cannot use both --message and --file',
|
|
16
16
|
INVALID_THREAD_TIMESTAMP: 'Invalid thread timestamp format',
|
|
17
|
+
INVALID_SCHEDULE_AT:
|
|
18
|
+
'Invalid schedule time format. Use Unix timestamp (seconds) or ISO 8601 date-time',
|
|
19
|
+
INVALID_SCHEDULE_AFTER: '--after must be a positive integer (minutes)',
|
|
20
|
+
BOTH_SCHEDULE_OPTIONS: 'Cannot use both --at and --after',
|
|
21
|
+
SCHEDULE_TIME_IN_PAST: 'Schedule time must be in the future',
|
|
17
22
|
|
|
18
23
|
// API errors
|
|
19
24
|
API_ERROR: (error: string) => `API Error: ${error}`,
|
|
@@ -33,6 +38,8 @@ export const SUCCESS_MESSAGES = {
|
|
|
33
38
|
PROFILE_SWITCHED: (profileName: string) => `Switched to profile "${profileName}"`,
|
|
34
39
|
PROFILE_CLEARED: (profileName: string) => `Profile "${profileName}" cleared successfully`,
|
|
35
40
|
MESSAGE_SENT: (channel: string) => `Message sent successfully to #${channel}`,
|
|
41
|
+
MESSAGE_SCHEDULED: (channel: string, postAtIso: string) =>
|
|
42
|
+
`Message scheduled to #${channel} at ${postAtIso}`,
|
|
36
43
|
} as const;
|
|
37
44
|
|
|
38
45
|
// File and system constants
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export function parseScheduledTimestamp(value: string): number | null {
|
|
2
|
+
const trimmed = value.trim();
|
|
3
|
+
|
|
4
|
+
if (/^\d+$/.test(trimmed)) {
|
|
5
|
+
const timestamp = Number.parseInt(trimmed, 10);
|
|
6
|
+
return Number.isSafeInteger(timestamp) ? timestamp : null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const parsedMs = Date.parse(trimmed);
|
|
10
|
+
if (Number.isNaN(parsedMs)) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return Math.floor(parsedMs / 1000);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function resolvePostAt(
|
|
18
|
+
at: string | undefined,
|
|
19
|
+
afterMinutes: string | undefined,
|
|
20
|
+
nowMs = Date.now()
|
|
21
|
+
): number | null {
|
|
22
|
+
if (at) {
|
|
23
|
+
return parseScheduledTimestamp(at);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!afterMinutes) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const trimmedAfter = afterMinutes.trim();
|
|
31
|
+
if (!/^\d+$/.test(trimmedAfter)) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const minutes = Number.parseInt(trimmedAfter, 10);
|
|
36
|
+
if (!Number.isSafeInteger(minutes) || minutes <= 0) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return Math.floor(nowMs / 1000) + minutes * 60;
|
|
41
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChatPostMessageResponse } from '@slack/web-api';
|
|
1
|
+
import { ChatPostMessageResponse, ChatScheduleMessageResponse } from '@slack/web-api';
|
|
2
2
|
import { ChannelOperations } from './slack-operations/channel-operations';
|
|
3
3
|
import { MessageOperations } from './slack-operations/message-operations';
|
|
4
4
|
|
|
@@ -57,6 +57,14 @@ export interface Message {
|
|
|
57
57
|
blocks?: unknown[];
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
export interface ScheduledMessage {
|
|
61
|
+
id: string;
|
|
62
|
+
channel_id: string;
|
|
63
|
+
post_at: number;
|
|
64
|
+
date_created: number;
|
|
65
|
+
text?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
60
68
|
export interface HistoryResult {
|
|
61
69
|
messages: Message[];
|
|
62
70
|
users: Map<string, string>;
|
|
@@ -85,6 +93,19 @@ export class SlackApiClient {
|
|
|
85
93
|
return this.messageOps.sendMessage(channel, text, thread_ts);
|
|
86
94
|
}
|
|
87
95
|
|
|
96
|
+
async scheduleMessage(
|
|
97
|
+
channel: string,
|
|
98
|
+
text: string,
|
|
99
|
+
post_at: number,
|
|
100
|
+
thread_ts?: string
|
|
101
|
+
): Promise<ChatScheduleMessageResponse> {
|
|
102
|
+
return this.messageOps.scheduleMessage(channel, text, post_at, thread_ts);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async listScheduledMessages(channel?: string, limit = 50): Promise<ScheduledMessage[]> {
|
|
106
|
+
return this.messageOps.listScheduledMessages(channel, limit);
|
|
107
|
+
}
|
|
108
|
+
|
|
88
109
|
async listChannels(options: ListChannelsOptions): Promise<Channel[]> {
|
|
89
110
|
return this.channelOps.listChannels(options);
|
|
90
111
|
}
|
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ChatPostMessageResponse,
|
|
3
|
+
ChatPostMessageArguments,
|
|
4
|
+
ChatScheduleMessageArguments,
|
|
5
|
+
ChatScheduleMessageResponse,
|
|
6
|
+
} from '@slack/web-api';
|
|
2
7
|
import { BaseSlackClient } from './base-client';
|
|
3
8
|
import { channelResolver } from '../channel-resolver';
|
|
4
9
|
import { DEFAULTS } from '../constants';
|
|
5
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
Message,
|
|
12
|
+
HistoryOptions,
|
|
13
|
+
HistoryResult,
|
|
14
|
+
ChannelUnreadResult,
|
|
15
|
+
ScheduledMessage,
|
|
16
|
+
} from '../slack-api-client';
|
|
6
17
|
import { ChannelOperations } from './channel-operations';
|
|
7
18
|
import { extractAllUserIds } from '../mention-utils';
|
|
8
19
|
|
|
@@ -31,6 +42,48 @@ export class MessageOperations extends BaseSlackClient {
|
|
|
31
42
|
return await this.client.chat.postMessage(params);
|
|
32
43
|
}
|
|
33
44
|
|
|
45
|
+
async scheduleMessage(
|
|
46
|
+
channel: string,
|
|
47
|
+
text: string,
|
|
48
|
+
post_at: number,
|
|
49
|
+
thread_ts?: string
|
|
50
|
+
): Promise<ChatScheduleMessageResponse> {
|
|
51
|
+
const params: ChatScheduleMessageArguments = {
|
|
52
|
+
channel,
|
|
53
|
+
text,
|
|
54
|
+
post_at,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (thread_ts) {
|
|
58
|
+
params.thread_ts = thread_ts;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return await this.client.chat.scheduleMessage(params);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async listScheduledMessages(channel?: string, limit = 50): Promise<ScheduledMessage[]> {
|
|
65
|
+
const channelId = channel
|
|
66
|
+
? await channelResolver.resolveChannelId(channel, () =>
|
|
67
|
+
this.channelOps.listChannels({
|
|
68
|
+
types: 'public_channel,private_channel,im,mpim',
|
|
69
|
+
exclude_archived: true,
|
|
70
|
+
limit: DEFAULTS.CHANNELS_LIMIT,
|
|
71
|
+
})
|
|
72
|
+
)
|
|
73
|
+
: undefined;
|
|
74
|
+
|
|
75
|
+
const params: { channel?: string; limit: number } = {
|
|
76
|
+
limit,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
if (channelId) {
|
|
80
|
+
params.channel = channelId;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const response = await this.client.chat.scheduledMessages.list(params as any);
|
|
84
|
+
return (response.scheduled_messages || []) as ScheduledMessage[];
|
|
85
|
+
}
|
|
86
|
+
|
|
34
87
|
async getHistory(channel: string, options: HistoryOptions): Promise<HistoryResult> {
|
|
35
88
|
// Resolve channel name to ID if needed
|
|
36
89
|
const channelId = await channelResolver.resolveChannelId(channel, () =>
|
package/src/utils/validators.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { ERROR_MESSAGES } from './constants';
|
|
3
|
+
import { parseScheduledTimestamp } from './schedule-utils';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Common validation functions for CLI commands
|
|
@@ -164,6 +165,43 @@ export const optionValidators = {
|
|
|
164
165
|
return null;
|
|
165
166
|
},
|
|
166
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Validates schedule options for send command
|
|
170
|
+
*/
|
|
171
|
+
scheduleTiming: (options: Record<string, unknown>): string | null => {
|
|
172
|
+
const at = options.at as string | undefined;
|
|
173
|
+
const after = options.after as string | undefined;
|
|
174
|
+
|
|
175
|
+
if (at && after) {
|
|
176
|
+
return ERROR_MESSAGES.BOTH_SCHEDULE_OPTIONS;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (at) {
|
|
180
|
+
const postAt = parseScheduledTimestamp(at);
|
|
181
|
+
if (postAt === null) {
|
|
182
|
+
return ERROR_MESSAGES.INVALID_SCHEDULE_AT;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (postAt <= Math.floor(Date.now() / 1000)) {
|
|
186
|
+
return ERROR_MESSAGES.SCHEDULE_TIME_IN_PAST;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (after) {
|
|
191
|
+
const trimmedAfter = after.trim();
|
|
192
|
+
if (!/^\d+$/.test(trimmedAfter)) {
|
|
193
|
+
return ERROR_MESSAGES.INVALID_SCHEDULE_AFTER;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const minutes = Number.parseInt(trimmedAfter, 10);
|
|
197
|
+
if (!Number.isSafeInteger(minutes) || minutes <= 0) {
|
|
198
|
+
return ERROR_MESSAGES.INVALID_SCHEDULE_AFTER;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return null;
|
|
203
|
+
},
|
|
204
|
+
|
|
167
205
|
/**
|
|
168
206
|
* Validates message count for history command
|
|
169
207
|
*/
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { setupScheduledCommand } from '../../src/commands/scheduled';
|
|
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
|
+
|
|
7
|
+
vi.mock('../../src/utils/slack-api-client');
|
|
8
|
+
vi.mock('../../src/utils/profile-config');
|
|
9
|
+
|
|
10
|
+
describe('scheduled command', () => {
|
|
11
|
+
let program: any;
|
|
12
|
+
let mockSlackClient: SlackApiClient;
|
|
13
|
+
let mockConfigManager: ProfileConfigManager;
|
|
14
|
+
let mockConsole: any;
|
|
15
|
+
let tableSpy: any;
|
|
16
|
+
|
|
17
|
+
const mockScheduledMessages = [
|
|
18
|
+
{
|
|
19
|
+
id: 'Q123',
|
|
20
|
+
channel_id: 'C1234567890',
|
|
21
|
+
post_at: 1770855000,
|
|
22
|
+
date_created: 1770854400,
|
|
23
|
+
text: 'Scheduled message 1',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'Q456',
|
|
27
|
+
channel_id: 'C0987654321',
|
|
28
|
+
post_at: 1770858600,
|
|
29
|
+
date_created: 1770854400,
|
|
30
|
+
text: 'Scheduled message 2',
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
vi.clearAllMocks();
|
|
36
|
+
|
|
37
|
+
mockConfigManager = new ProfileConfigManager();
|
|
38
|
+
vi.mocked(ProfileConfigManager).mockReturnValue(mockConfigManager);
|
|
39
|
+
|
|
40
|
+
mockSlackClient = new SlackApiClient('test-token');
|
|
41
|
+
vi.mocked(SlackApiClient).mockReturnValue(mockSlackClient);
|
|
42
|
+
|
|
43
|
+
mockConsole = setupMockConsole();
|
|
44
|
+
tableSpy = vi.spyOn(console, 'table').mockImplementation(() => {});
|
|
45
|
+
|
|
46
|
+
program = createTestProgram();
|
|
47
|
+
program.addCommand(setupScheduledCommand());
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
restoreMocks();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should list scheduled messages in table format by default', async () => {
|
|
55
|
+
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
56
|
+
token: 'test-token',
|
|
57
|
+
updatedAt: new Date().toISOString(),
|
|
58
|
+
});
|
|
59
|
+
vi.mocked(mockSlackClient.listScheduledMessages).mockResolvedValue(
|
|
60
|
+
mockScheduledMessages as any
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
await program.parseAsync(['node', 'slack-cli', 'scheduled']);
|
|
64
|
+
|
|
65
|
+
expect(mockSlackClient.listScheduledMessages).toHaveBeenCalledWith(undefined, 50);
|
|
66
|
+
expect(tableSpy).toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should filter by channel and limit', async () => {
|
|
70
|
+
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
71
|
+
token: 'test-token',
|
|
72
|
+
updatedAt: new Date().toISOString(),
|
|
73
|
+
});
|
|
74
|
+
vi.mocked(mockSlackClient.listScheduledMessages).mockResolvedValue(
|
|
75
|
+
mockScheduledMessages as any
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
await program.parseAsync([
|
|
79
|
+
'node',
|
|
80
|
+
'slack-cli',
|
|
81
|
+
'scheduled',
|
|
82
|
+
'--channel',
|
|
83
|
+
'general',
|
|
84
|
+
'--limit',
|
|
85
|
+
'10',
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
expect(mockSlackClient.listScheduledMessages).toHaveBeenCalledWith('general', 10);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should output json format', async () => {
|
|
92
|
+
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
93
|
+
token: 'test-token',
|
|
94
|
+
updatedAt: new Date().toISOString(),
|
|
95
|
+
});
|
|
96
|
+
vi.mocked(mockSlackClient.listScheduledMessages).mockResolvedValue(
|
|
97
|
+
mockScheduledMessages as any
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
await program.parseAsync(['node', 'slack-cli', 'scheduled', '--format', 'json']);
|
|
101
|
+
|
|
102
|
+
expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('"id": "Q123"'));
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should output simple format', async () => {
|
|
106
|
+
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
107
|
+
token: 'test-token',
|
|
108
|
+
updatedAt: new Date().toISOString(),
|
|
109
|
+
});
|
|
110
|
+
vi.mocked(mockSlackClient.listScheduledMessages).mockResolvedValue(
|
|
111
|
+
mockScheduledMessages as any
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
await program.parseAsync(['node', 'slack-cli', 'scheduled', '--format', 'simple']);
|
|
115
|
+
|
|
116
|
+
expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Q123'));
|
|
117
|
+
expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Q456'));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should show empty message when no scheduled messages', async () => {
|
|
121
|
+
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
122
|
+
token: 'test-token',
|
|
123
|
+
updatedAt: new Date().toISOString(),
|
|
124
|
+
});
|
|
125
|
+
vi.mocked(mockSlackClient.listScheduledMessages).mockResolvedValue([] as any);
|
|
126
|
+
|
|
127
|
+
await program.parseAsync(['node', 'slack-cli', 'scheduled']);
|
|
128
|
+
|
|
129
|
+
expect(mockConsole.logSpy).toHaveBeenCalledWith('No scheduled messages found');
|
|
130
|
+
});
|
|
131
|
+
});
|