@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,263 +0,0 @@
1
- import { Command } from 'commander';
2
- import { ERROR_MESSAGES } from './constants';
3
- import { parseScheduledTimestamp } from './schedule-utils';
4
-
5
- /**
6
- * Common validation functions for CLI commands
7
- */
8
-
9
- export interface ValidationRule<T = unknown> {
10
- validate: (value: T) => boolean | string;
11
- errorMessage?: string;
12
- }
13
-
14
- export interface ValidationOptions {
15
- required?: boolean;
16
- rules?: ValidationRule[];
17
- }
18
-
19
- /**
20
- * Validates that a value exists (not undefined, null, or empty string)
21
- */
22
- export function validateRequired(value: unknown, fieldName: string): string | null {
23
- if (value === undefined || value === null || value === '') {
24
- return `${fieldName} is required`;
25
- }
26
- return null;
27
- }
28
-
29
- /**
30
- * Validates mutually exclusive options
31
- */
32
- export function validateMutuallyExclusive(
33
- options: Record<string, unknown>,
34
- fields: string[],
35
- errorMessage?: string
36
- ): string | null {
37
- const presentFields = fields.filter((field) => options[field] !== undefined);
38
- if (presentFields.length > 1) {
39
- return errorMessage || `Cannot use both ${presentFields.join(' and ')}`;
40
- }
41
- if (presentFields.length === 0) {
42
- return errorMessage || `Must specify one of: ${fields.join(', ')}`;
43
- }
44
- return null;
45
- }
46
-
47
- /**
48
- * Validates string format using regex
49
- */
50
- export function validateFormat(
51
- value: string,
52
- pattern: RegExp,
53
- errorMessage: string
54
- ): string | null {
55
- if (!pattern.test(value)) {
56
- return errorMessage;
57
- }
58
- return null;
59
- }
60
-
61
- /**
62
- * Validates numeric range
63
- */
64
- export function validateRange(
65
- value: number,
66
- min?: number,
67
- max?: number,
68
- fieldName = 'Value'
69
- ): string | null {
70
- if (min !== undefined && value < min) {
71
- return `${fieldName} must be at least ${min}`;
72
- }
73
- if (max !== undefined && value > max) {
74
- return `${fieldName} must be at most ${max}`;
75
- }
76
- return null;
77
- }
78
-
79
- /**
80
- * Validates date format
81
- */
82
- export function validateDateFormat(dateString: string): string | null {
83
- const date = new Date(dateString);
84
- if (isNaN(date.getTime())) {
85
- return 'Invalid date format';
86
- }
87
- return null;
88
- }
89
-
90
- /**
91
- * Common format validators
92
- */
93
- export const formatValidators = {
94
- /**
95
- * Validates Slack thread timestamp format (1234567890.123456)
96
- */
97
- threadTimestamp: (value: string): string | null => {
98
- const pattern = /^\d{10}\.\d{6}$/;
99
- return validateFormat(value, pattern, ERROR_MESSAGES.INVALID_THREAD_TIMESTAMP);
100
- },
101
-
102
- /**
103
- * Validates Slack channel ID format (C1234567890, D1234567890, G1234567890)
104
- */
105
- channelId: (value: string): string | null => {
106
- const pattern = /^[CDG][A-Z0-9]{10,}$/;
107
- return validateFormat(value, pattern, 'Invalid channel ID format');
108
- },
109
-
110
- /**
111
- * Validates output format options
112
- */
113
- outputFormat: (value: string): string | null => {
114
- const validFormats = ['table', 'simple', 'json', 'compact'];
115
- if (!validFormats.includes(value)) {
116
- return `Invalid format. Must be one of: ${validFormats.join(', ')}`;
117
- }
118
- return null;
119
- },
120
- };
121
-
122
- /**
123
- * Creates a preAction hook for command validation
124
- */
125
- export function createValidationHook(
126
- validations: Array<(options: Record<string, unknown>, command: Command) => string | null>
127
- ): (thisCommand: Command) => void {
128
- return (thisCommand: Command) => {
129
- const options = thisCommand.opts();
130
-
131
- for (const validation of validations) {
132
- const error = validation(options, thisCommand);
133
- if (error) {
134
- thisCommand.error(`Error: ${error}`);
135
- break; // Stop processing after first error
136
- }
137
- }
138
- };
139
- }
140
-
141
- /**
142
- * Common command option validators
143
- */
144
- export const optionValidators = {
145
- /**
146
- * Validates message/file options for send command
147
- */
148
- messageOrFile: (options: Record<string, unknown>): string | null => {
149
- if (!options.message && !options.file) {
150
- return ERROR_MESSAGES.NO_MESSAGE_OR_FILE;
151
- }
152
- if (options.message && options.file) {
153
- return ERROR_MESSAGES.BOTH_MESSAGE_AND_FILE;
154
- }
155
- return null;
156
- },
157
-
158
- /**
159
- * Validates thread timestamp if provided
160
- */
161
- threadTimestamp: (options: Record<string, unknown>): string | null => {
162
- if (options.thread) {
163
- return formatValidators.threadTimestamp(options.thread as string);
164
- }
165
- return null;
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
-
205
- /**
206
- * Validates message count for history command
207
- */
208
- messageCount: (options: Record<string, unknown>): string | null => {
209
- if (options.number) {
210
- const count = parseInt(options.number as string, 10);
211
- if (isNaN(count)) {
212
- return 'Message count must be a number';
213
- }
214
- return validateRange(count, 1, 1000, 'Message count');
215
- }
216
- return null;
217
- },
218
-
219
- /**
220
- * Validates date format for history command
221
- */
222
- sinceDate: (options: Record<string, unknown>): string | null => {
223
- if (options.since) {
224
- const error = validateDateFormat(options.since as string);
225
- if (error) {
226
- return 'Invalid date format. Use YYYY-MM-DD HH:MM:SS';
227
- }
228
- }
229
- return null;
230
- },
231
-
232
- /**
233
- * Validates format option
234
- */
235
- format: (options: Record<string, unknown>): string | null => {
236
- if (options.format) {
237
- const validFormats = ['table', 'simple', 'json'];
238
- if (!validFormats.includes(options.format as string)) {
239
- return `Invalid format '${options.format}'. Must be one of: ${validFormats.join(', ')}`;
240
- }
241
- }
242
- return null;
243
- },
244
- };
245
-
246
- /**
247
- * Creates a validated option parser
248
- */
249
- export function createOptionParser<T>(
250
- parser: (value: string | undefined, defaultValue: T) => T,
251
- validator?: (value: T) => string | null
252
- ): (value: string | undefined, defaultValue: T) => T {
253
- return (value: string | undefined, defaultValue: T): T => {
254
- const parsed = parser(value, defaultValue);
255
- if (validator) {
256
- const error = validator(parsed);
257
- if (error) {
258
- throw new Error(error);
259
- }
260
- }
261
- return parsed;
262
- };
263
- }
@@ -1,250 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { setupChannelsCommand } from '../../src/commands/channels';
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
-
8
- vi.mock('../../src/utils/slack-api-client');
9
- vi.mock('../../src/utils/profile-config');
10
-
11
- describe('channels command', () => {
12
- let program: any;
13
- let mockSlackClient: SlackApiClient;
14
- let mockConfigManager: ProfileConfigManager;
15
- let mockConsole: any;
16
-
17
- beforeEach(() => {
18
- vi.clearAllMocks();
19
-
20
- mockConfigManager = new ProfileConfigManager();
21
- vi.mocked(ProfileConfigManager).mockReturnValue(mockConfigManager);
22
-
23
- mockSlackClient = new SlackApiClient('test-token');
24
- vi.mocked(SlackApiClient).mockReturnValue(mockSlackClient);
25
-
26
- mockConsole = setupMockConsole();
27
- program = createTestProgram();
28
- program.addCommand(setupChannelsCommand());
29
- });
30
-
31
- afterEach(() => {
32
- restoreMocks();
33
- });
34
-
35
- const mockChannels = [
36
- {
37
- id: 'C1234567890',
38
- name: 'general',
39
- is_channel: true,
40
- is_private: false,
41
- num_members: 250,
42
- created: 1579075200,
43
- purpose: { value: 'Company announcements' }
44
- },
45
- {
46
- id: 'C0987654321',
47
- name: 'random',
48
- is_channel: true,
49
- is_private: false,
50
- num_members: 145,
51
- created: 1579075200,
52
- purpose: { value: 'Random discussions' }
53
- }
54
- ];
55
-
56
- describe('basic functionality', () => {
57
- it('should list public channels by default', async () => {
58
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
59
- token: 'test-token',
60
- updatedAt: new Date().toISOString()
61
- });
62
- vi.mocked(mockSlackClient.listChannels).mockResolvedValue(mockChannels);
63
-
64
- await program.parseAsync(['node', 'slack-cli', 'channels']);
65
-
66
- expect(mockSlackClient.listChannels).toHaveBeenCalledWith({
67
- types: 'public_channel',
68
- exclude_archived: true,
69
- limit: 100
70
- });
71
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('general'));
72
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('random'));
73
- });
74
-
75
- it('should show error when no token is configured', async () => {
76
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue(null);
77
- vi.mocked(mockConfigManager.listProfiles).mockResolvedValue([]);
78
-
79
- await program.parseAsync(['node', 'slack-cli', 'channels']);
80
-
81
- expect(mockConsole.errorSpy).toHaveBeenCalledWith('✗ Error:', ERROR_MESSAGES.NO_CONFIG('default'));
82
- expect(mockConsole.exitSpy).toHaveBeenCalledWith(1);
83
- });
84
- });
85
-
86
- describe('channel type filtering', () => {
87
- it('should list private channels when type is private', async () => {
88
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
89
- token: 'test-token',
90
- updatedAt: new Date().toISOString()
91
- });
92
- vi.mocked(mockSlackClient.listChannels).mockResolvedValue(mockChannels);
93
-
94
- await program.parseAsync(['node', 'slack-cli', 'channels', '--type', 'private']);
95
-
96
- expect(mockSlackClient.listChannels).toHaveBeenCalledWith({
97
- types: 'private_channel',
98
- exclude_archived: true,
99
- limit: 100
100
- });
101
- });
102
-
103
- it('should list all channel types when type is all', async () => {
104
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
105
- token: 'test-token',
106
- updatedAt: new Date().toISOString()
107
- });
108
- vi.mocked(mockSlackClient.listChannels).mockResolvedValue(mockChannels);
109
-
110
- await program.parseAsync(['node', 'slack-cli', 'channels', '--type', 'all']);
111
-
112
- expect(mockSlackClient.listChannels).toHaveBeenCalledWith({
113
- types: 'public_channel,private_channel,mpim,im',
114
- exclude_archived: true,
115
- limit: 100
116
- });
117
- });
118
-
119
- it('should list direct messages when type is im', async () => {
120
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
121
- token: 'test-token',
122
- updatedAt: new Date().toISOString()
123
- });
124
- vi.mocked(mockSlackClient.listChannels).mockResolvedValue(mockChannels);
125
-
126
- await program.parseAsync(['node', 'slack-cli', 'channels', '--type', 'im']);
127
-
128
- expect(mockSlackClient.listChannels).toHaveBeenCalledWith({
129
- types: 'im',
130
- exclude_archived: true,
131
- limit: 100
132
- });
133
- });
134
- });
135
-
136
- describe('output formatting', () => {
137
- it('should output in table format by default', async () => {
138
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
139
- token: 'test-token',
140
- updatedAt: new Date().toISOString()
141
- });
142
- vi.mocked(mockSlackClient.listChannels).mockResolvedValue(mockChannels);
143
-
144
- await program.parseAsync(['node', 'slack-cli', 'channels']);
145
-
146
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Name'));
147
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Type'));
148
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Members'));
149
- });
150
-
151
- it('should output in simple format when specified', async () => {
152
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
153
- token: 'test-token',
154
- updatedAt: new Date().toISOString()
155
- });
156
- vi.mocked(mockSlackClient.listChannels).mockResolvedValue(mockChannels);
157
-
158
- await program.parseAsync(['node', 'slack-cli', 'channels', '--format', 'simple']);
159
-
160
- expect(mockConsole.logSpy).toHaveBeenCalledWith('general');
161
- expect(mockConsole.logSpy).toHaveBeenCalledWith('random');
162
- });
163
-
164
- it('should output in JSON format when specified', async () => {
165
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
166
- token: 'test-token',
167
- updatedAt: new Date().toISOString()
168
- });
169
- vi.mocked(mockSlackClient.listChannels).mockResolvedValue(mockChannels);
170
-
171
- await program.parseAsync(['node', 'slack-cli', 'channels', '--format', 'json']);
172
-
173
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('"name": "general"'));
174
- });
175
- });
176
-
177
- describe('additional options', () => {
178
- it('should include archived channels when flag is set', async () => {
179
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
180
- token: 'test-token',
181
- updatedAt: new Date().toISOString()
182
- });
183
- vi.mocked(mockSlackClient.listChannels).mockResolvedValue(mockChannels);
184
-
185
- await program.parseAsync(['node', 'slack-cli', 'channels', '--include-archived']);
186
-
187
- expect(mockSlackClient.listChannels).toHaveBeenCalledWith({
188
- types: 'public_channel',
189
- exclude_archived: false,
190
- limit: 100
191
- });
192
- });
193
-
194
- it('should respect custom limit', async () => {
195
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
196
- token: 'test-token',
197
- updatedAt: new Date().toISOString()
198
- });
199
- vi.mocked(mockSlackClient.listChannels).mockResolvedValue(mockChannels);
200
-
201
- await program.parseAsync(['node', 'slack-cli', 'channels', '--limit', '50']);
202
-
203
- expect(mockSlackClient.listChannels).toHaveBeenCalledWith({
204
- types: 'public_channel',
205
- exclude_archived: true,
206
- limit: 50
207
- });
208
- });
209
-
210
- it('should use specified profile', async () => {
211
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
212
- token: 'work-token',
213
- updatedAt: new Date().toISOString()
214
- });
215
- vi.mocked(mockSlackClient.listChannels).mockResolvedValue(mockChannels);
216
-
217
- await program.parseAsync(['node', 'slack-cli', 'channels', '--profile', 'work']);
218
-
219
- expect(mockConfigManager.getConfig).toHaveBeenCalledWith('work');
220
- expect(SlackApiClient).toHaveBeenCalledWith('work-token');
221
- });
222
- });
223
-
224
- describe('error handling', () => {
225
- it('should handle API errors gracefully', async () => {
226
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
227
- token: 'test-token',
228
- updatedAt: new Date().toISOString()
229
- });
230
- vi.mocked(mockSlackClient.listChannels).mockRejectedValue(new Error('API Error'));
231
-
232
- await program.parseAsync(['node', 'slack-cli', 'channels']);
233
-
234
- expect(mockConsole.errorSpy).toHaveBeenCalledWith('✗ Error:', 'API Error');
235
- expect(mockConsole.exitSpy).toHaveBeenCalledWith(1);
236
- });
237
-
238
- it('should show message when no channels found', async () => {
239
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
240
- token: 'test-token',
241
- updatedAt: new Date().toISOString()
242
- });
243
- vi.mocked(mockSlackClient.listChannels).mockResolvedValue([]);
244
-
245
- await program.parseAsync(['node', 'slack-cli', 'channels']);
246
-
247
- expect(mockConsole.logSpy).toHaveBeenCalledWith(ERROR_MESSAGES.NO_CHANNELS_FOUND);
248
- });
249
- });
250
- });
@@ -1,158 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import { setupConfigCommand } from '../../src/commands/config';
3
- import { ProfileConfigManager } from '../../src/utils/profile-config';
4
- import type { Config, Profile } from '../../src/types/config';
5
- import { setupMockConsole, createTestProgram, restoreMocks } from '../test-utils';
6
- import { SUCCESS_MESSAGES, ERROR_MESSAGES } from '../../src/utils/constants';
7
-
8
- vi.mock('../../src/utils/profile-config');
9
-
10
- describe('profile config command', () => {
11
- let program: any;
12
- let mockConfigManager: ProfileConfigManager;
13
- let mockConsole: any;
14
-
15
- beforeEach(() => {
16
- vi.clearAllMocks();
17
-
18
- mockConfigManager = new ProfileConfigManager();
19
- vi.mocked(ProfileConfigManager).mockReturnValue(mockConfigManager);
20
-
21
- mockConsole = setupMockConsole();
22
- program = createTestProgram();
23
- program.addCommand(setupConfigCommand());
24
- });
25
-
26
- afterEach(() => {
27
- restoreMocks();
28
- });
29
-
30
- describe('config set with profile', () => {
31
- it('should set token for specified profile', async () => {
32
- vi.mocked(mockConfigManager.setToken).mockResolvedValue(undefined);
33
-
34
- await program.parseAsync(['node', 'slack-cli', 'config', 'set', '--token', 'test-token-123', '--profile', 'work']);
35
-
36
- expect(mockConfigManager.setToken).toHaveBeenCalledWith('test-token-123', 'work');
37
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining(SUCCESS_MESSAGES.TOKEN_SAVED('work')));
38
- });
39
-
40
- it('should set token for default profile when no profile specified', async () => {
41
- vi.mocked(mockConfigManager.setToken).mockResolvedValue(undefined);
42
- vi.mocked(mockConfigManager.getCurrentProfile).mockResolvedValue('default');
43
-
44
- await program.parseAsync(['node', 'slack-cli', 'config', 'set', '--token', 'test-token-123']);
45
-
46
- expect(mockConfigManager.setToken).toHaveBeenCalledWith('test-token-123', undefined);
47
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Token saved successfully for profile "default"'));
48
- });
49
- });
50
-
51
- describe('config get with profile', () => {
52
- it('should display config for specified profile', async () => {
53
- const mockConfig: Config = {
54
- token: 'test-token-1234567890-abcdefghijklmnop',
55
- updatedAt: '2025-06-21T10:00:00.000Z'
56
- };
57
-
58
- vi.mocked(mockConfigManager.getConfig).mockResolvedValue(mockConfig);
59
- vi.mocked(mockConfigManager.maskToken).mockReturnValue('test-****-****-mnop');
60
-
61
- await program.parseAsync(['node', 'slack-cli', 'config', 'get', '--profile', 'work']);
62
-
63
- expect(mockConfigManager.getConfig).toHaveBeenCalledWith('work');
64
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Configuration for profile "work":'));
65
- });
66
- });
67
-
68
- describe('config profiles', () => {
69
- it('should list all profiles', async () => {
70
- const mockProfiles: Profile[] = [
71
- {
72
- name: 'default',
73
- config: {
74
- token: 'default-token',
75
- updatedAt: '2025-06-21T10:00:00.000Z'
76
- }
77
- },
78
- {
79
- name: 'work',
80
- config: {
81
- token: 'work-token',
82
- updatedAt: '2025-06-21T11:00:00.000Z'
83
- }
84
- }
85
- ];
86
-
87
- vi.mocked(mockConfigManager.listProfiles).mockResolvedValue(mockProfiles);
88
- vi.mocked(mockConfigManager.getCurrentProfile).mockResolvedValue('work');
89
-
90
- await program.parseAsync(['node', 'slack-cli', 'config', 'profiles']);
91
-
92
- expect(mockConfigManager.listProfiles).toHaveBeenCalled();
93
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Available profiles:'));
94
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('default'));
95
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('* work')); // current profile marked
96
- });
97
-
98
- it('should show message when no profiles exist', async () => {
99
- vi.mocked(mockConfigManager.listProfiles).mockResolvedValue([]);
100
-
101
- await program.parseAsync(['node', 'slack-cli', 'config', 'profiles']);
102
-
103
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('No profiles found'));
104
- });
105
- });
106
-
107
- describe('config use', () => {
108
- it('should switch default profile', async () => {
109
- vi.mocked(mockConfigManager.useProfile).mockResolvedValue(undefined);
110
-
111
- await program.parseAsync(['node', 'slack-cli', 'config', 'use', 'work']);
112
-
113
- expect(mockConfigManager.useProfile).toHaveBeenCalledWith('work');
114
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Switched to profile "work"'));
115
- });
116
-
117
- it('should show error when profile does not exist', async () => {
118
- vi.mocked(mockConfigManager.useProfile).mockRejectedValue(new Error('Profile "nonexistent" does not exist'));
119
-
120
- await program.parseAsync(['node', 'slack-cli', 'config', 'use', 'nonexistent']);
121
-
122
- expect(mockConsole.errorSpy).toHaveBeenCalledWith(expect.stringContaining('Error:'), expect.any(String));
123
- expect(mockConsole.exitSpy).toHaveBeenCalledWith(1);
124
- });
125
- });
126
-
127
- describe('config current', () => {
128
- it('should show current active profile', async () => {
129
- vi.mocked(mockConfigManager.getCurrentProfile).mockResolvedValue('work');
130
-
131
- await program.parseAsync(['node', 'slack-cli', 'config', 'current']);
132
-
133
- expect(mockConfigManager.getCurrentProfile).toHaveBeenCalled();
134
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Current profile: work'));
135
- });
136
- });
137
-
138
- describe('config clear with profile', () => {
139
- it('should clear specific profile', async () => {
140
- vi.mocked(mockConfigManager.clearConfig).mockResolvedValue(undefined);
141
-
142
- await program.parseAsync(['node', 'slack-cli', 'config', 'clear', '--profile', 'work']);
143
-
144
- expect(mockConfigManager.clearConfig).toHaveBeenCalledWith('work');
145
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Profile "work" cleared successfully'));
146
- });
147
-
148
- it('should clear current profile when no profile specified', async () => {
149
- vi.mocked(mockConfigManager.clearConfig).mockResolvedValue(undefined);
150
- vi.mocked(mockConfigManager.getCurrentProfile).mockResolvedValue('default');
151
-
152
- await program.parseAsync(['node', 'slack-cli', 'config', 'clear']);
153
-
154
- expect(mockConfigManager.clearConfig).toHaveBeenCalledWith(undefined);
155
- expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Profile "default" cleared successfully'));
156
- });
157
- });
158
- });