@urugus/slack-cli 0.2.11 → 0.2.13
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/README.md +4 -0
- package/dist/commands/history-display.d.ts +5 -1
- package/dist/commands/history-display.d.ts.map +1 -1
- package/dist/commands/history-display.js +3 -3
- package/dist/commands/history-display.js.map +1 -1
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +28 -11
- package/dist/commands/history.js.map +1 -1
- package/dist/types/commands.d.ts +1 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/utils/slack-api-client.d.ts +1 -0
- package/dist/utils/slack-api-client.d.ts.map +1 -1
- package/dist/utils/slack-api-client.js +3 -0
- package/dist/utils/slack-api-client.js.map +1 -1
- package/dist/utils/slack-operations/message-operations.d.ts +1 -0
- package/dist/utils/slack-operations/message-operations.d.ts.map +1 -1
- package/dist/utils/slack-operations/message-operations.js +21 -0
- package/dist/utils/slack-operations/message-operations.js.map +1 -1
- package/package.json +5 -2
- package/.claude/settings.local.json +0 -75
- package/.github/dependabot.yml +0 -18
- package/.github/workflows/ci.yml +0 -70
- package/.github/workflows/pr-validation.yml +0 -41
- package/.prettierignore +0 -11
- package/.prettierrc +0 -10
- package/CHANGELOG.md +0 -61
- package/CLAUDE.md +0 -16
- package/eslint.config.js +0 -38
- package/src/commands/channels.ts +0 -50
- package/src/commands/config-subcommands.ts +0 -63
- package/src/commands/config.ts +0 -50
- package/src/commands/history-display.ts +0 -19
- package/src/commands/history-validators.ts +0 -46
- package/src/commands/history.ts +0 -61
- package/src/commands/scheduled.ts +0 -71
- package/src/commands/send.ts +0 -69
- package/src/commands/unread.ts +0 -122
- package/src/index.ts +0 -27
- package/src/types/commands.ts +0 -58
- package/src/types/config.ts +0 -20
- package/src/utils/channel-formatter.ts +0 -45
- package/src/utils/channel-resolver.ts +0 -82
- package/src/utils/client-factory.ts +0 -10
- package/src/utils/command-wrapper.ts +0 -27
- package/src/utils/config/config-file-manager.ts +0 -56
- package/src/utils/config/profile-manager.ts +0 -79
- package/src/utils/config/token-crypto-service.ts +0 -80
- package/src/utils/config-helper.ts +0 -21
- package/src/utils/constants.ts +0 -78
- package/src/utils/date-utils.ts +0 -8
- package/src/utils/error-utils.ts +0 -6
- package/src/utils/errors.ts +0 -33
- package/src/utils/format-utils.ts +0 -9
- package/src/utils/formatters/base-formatter.ts +0 -34
- package/src/utils/formatters/channel-formatters.ts +0 -71
- package/src/utils/formatters/channels-list-formatters.ts +0 -55
- package/src/utils/formatters/history-formatters.ts +0 -123
- package/src/utils/formatters/message-formatters.ts +0 -85
- package/src/utils/mention-utils.ts +0 -47
- package/src/utils/option-parsers.ts +0 -100
- package/src/utils/profile-config.ts +0 -161
- package/src/utils/schedule-utils.ts +0 -41
- package/src/utils/slack-api-client.ts +0 -135
- package/src/utils/slack-operations/base-client.ts +0 -30
- package/src/utils/slack-operations/channel-operations.ts +0 -161
- package/src/utils/slack-operations/index.ts +0 -3
- package/src/utils/slack-operations/message-operations.ts +0 -176
- package/src/utils/slack-patterns.ts +0 -9
- package/src/utils/token-utils.ts +0 -17
- package/src/utils/validators.ts +0 -263
- package/tests/commands/channels.test.ts +0 -250
- package/tests/commands/config.test.ts +0 -158
- package/tests/commands/history.test.ts +0 -403
- package/tests/commands/scheduled.test.ts +0 -131
- package/tests/commands/send.test.ts +0 -414
- package/tests/commands/unread.test.ts +0 -492
- package/tests/index.test.ts +0 -40
- package/tests/test-utils.ts +0 -28
- package/tests/utils/channel-resolver.test.ts +0 -161
- package/tests/utils/config/config-file-manager.test.ts +0 -118
- package/tests/utils/config/profile-manager.test.ts +0 -266
- package/tests/utils/config/token-crypto-service.test.ts +0 -98
- package/tests/utils/config.test.ts +0 -400
- package/tests/utils/date-utils.test.ts +0 -30
- package/tests/utils/error-utils.test.ts +0 -34
- package/tests/utils/format-utils.test.ts +0 -61
- package/tests/utils/mention-utils.test.ts +0 -100
- package/tests/utils/option-parsers.test.ts +0 -173
- package/tests/utils/profile-config.test.ts +0 -282
- package/tests/utils/schedule-utils.test.ts +0 -63
- package/tests/utils/slack-api-client.test.ts +0 -313
- package/tests/utils/slack-operations/channel-operations.test.ts +0 -248
- package/tests/utils/slack-operations/message-operations.test.ts +0 -163
- package/tests/utils/token-utils.test.ts +0 -33
- package/tests/utils/validators.test.ts +0 -307
- package/tsconfig.json +0 -22
- package/vitest.config.ts +0 -27
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
parseFormat,
|
|
4
|
-
parseLimit,
|
|
5
|
-
parseBoolean,
|
|
6
|
-
parseCount,
|
|
7
|
-
parseProfile,
|
|
8
|
-
parseListOptions,
|
|
9
|
-
OPTION_DEFAULTS,
|
|
10
|
-
} from '../../src/utils/option-parsers';
|
|
11
|
-
|
|
12
|
-
describe('option-parsers', () => {
|
|
13
|
-
describe('parseFormat', () => {
|
|
14
|
-
it('should return default format when undefined', () => {
|
|
15
|
-
expect(parseFormat(undefined)).toBe('table');
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('should return custom default format when specified', () => {
|
|
19
|
-
expect(parseFormat(undefined, 'json')).toBe('json');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should return provided format', () => {
|
|
23
|
-
expect(parseFormat('compact')).toBe('compact');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should handle empty string', () => {
|
|
27
|
-
expect(parseFormat('')).toBe('table');
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
describe('parseLimit', () => {
|
|
32
|
-
it('should return default limit when undefined', () => {
|
|
33
|
-
expect(parseLimit(undefined, 100)).toBe(100);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should parse string limit', () => {
|
|
37
|
-
expect(parseLimit('50', 100)).toBe(50);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('should handle invalid number string', () => {
|
|
41
|
-
expect(parseLimit('abc', 100)).toBe(NaN);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should handle empty string', () => {
|
|
45
|
-
expect(parseLimit('', 100)).toBe(100);
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe('parseBoolean', () => {
|
|
50
|
-
it('should return default value when undefined', () => {
|
|
51
|
-
expect(parseBoolean(undefined)).toBe(false);
|
|
52
|
-
expect(parseBoolean(undefined, true)).toBe(true);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should return provided boolean value', () => {
|
|
56
|
-
expect(parseBoolean(true)).toBe(true);
|
|
57
|
-
expect(parseBoolean(false)).toBe(false);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('should handle explicit false over default true', () => {
|
|
61
|
-
expect(parseBoolean(false, true)).toBe(false);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe('parseCount', () => {
|
|
66
|
-
it('should return default count when undefined', () => {
|
|
67
|
-
expect(parseCount(undefined, 10)).toBe(10);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should parse string count', () => {
|
|
71
|
-
expect(parseCount('25', 10)).toBe(25);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should handle invalid number string', () => {
|
|
75
|
-
expect(parseCount('invalid', 10)).toBe(10);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('should enforce minimum value', () => {
|
|
79
|
-
expect(parseCount('5', 10, 10, 100)).toBe(10);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('should enforce maximum value', () => {
|
|
83
|
-
expect(parseCount('150', 10, 10, 100)).toBe(100);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('should allow values within range', () => {
|
|
87
|
-
expect(parseCount('50', 10, 10, 100)).toBe(50);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('should handle empty string', () => {
|
|
91
|
-
expect(parseCount('', 20)).toBe(20);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe('parseProfile', () => {
|
|
96
|
-
it('should return undefined when not provided', () => {
|
|
97
|
-
expect(parseProfile(undefined)).toBeUndefined();
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('should return provided profile', () => {
|
|
101
|
-
expect(parseProfile('production')).toBe('production');
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('should handle empty string', () => {
|
|
105
|
-
expect(parseProfile('')).toBe('');
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
describe('parseListOptions', () => {
|
|
110
|
-
it('should use all defaults when no options provided', () => {
|
|
111
|
-
const result = parseListOptions({});
|
|
112
|
-
expect(result).toEqual({
|
|
113
|
-
format: 'table',
|
|
114
|
-
limit: 100,
|
|
115
|
-
countOnly: false,
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('should override specific options', () => {
|
|
120
|
-
const result = parseListOptions({
|
|
121
|
-
format: 'json',
|
|
122
|
-
limit: '50',
|
|
123
|
-
countOnly: true,
|
|
124
|
-
});
|
|
125
|
-
expect(result).toEqual({
|
|
126
|
-
format: 'json',
|
|
127
|
-
limit: 50,
|
|
128
|
-
countOnly: true,
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('should use custom defaults', () => {
|
|
133
|
-
const result = parseListOptions({}, {
|
|
134
|
-
format: 'compact',
|
|
135
|
-
limit: 200,
|
|
136
|
-
countOnly: true,
|
|
137
|
-
});
|
|
138
|
-
expect(result).toEqual({
|
|
139
|
-
format: 'compact',
|
|
140
|
-
limit: 200,
|
|
141
|
-
countOnly: true,
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('should override custom defaults with provided options', () => {
|
|
146
|
-
const result = parseListOptions(
|
|
147
|
-
{
|
|
148
|
-
format: 'json',
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
format: 'compact',
|
|
152
|
-
limit: 200,
|
|
153
|
-
}
|
|
154
|
-
);
|
|
155
|
-
expect(result).toEqual({
|
|
156
|
-
format: 'json',
|
|
157
|
-
limit: 200,
|
|
158
|
-
countOnly: false,
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
describe('OPTION_DEFAULTS', () => {
|
|
164
|
-
it('should have expected default values', () => {
|
|
165
|
-
expect(OPTION_DEFAULTS).toEqual({
|
|
166
|
-
format: 'table',
|
|
167
|
-
limit: 100,
|
|
168
|
-
countOnly: false,
|
|
169
|
-
includeArchived: false,
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
});
|
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import * as fs from 'fs/promises';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import * as os from 'os';
|
|
5
|
-
import { ProfileConfigManager } from '../../src/utils/profile-config';
|
|
6
|
-
|
|
7
|
-
vi.mock('fs/promises');
|
|
8
|
-
vi.mock('os');
|
|
9
|
-
|
|
10
|
-
describe('ProfileConfigManager', () => {
|
|
11
|
-
let configManager: ProfileConfigManager;
|
|
12
|
-
const mockConfigPath = '/home/user/.slack-cli/config.json';
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
vi.resetAllMocks();
|
|
16
|
-
vi.mocked(os.homedir).mockReturnValue('/home/user');
|
|
17
|
-
configManager = new ProfileConfigManager();
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
describe('setToken', () => {
|
|
21
|
-
it('should set token for default profile when no profile specified', async () => {
|
|
22
|
-
vi.mocked(fs.readFile).mockRejectedValueOnce({ code: 'ENOENT' });
|
|
23
|
-
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
24
|
-
vi.mocked(fs.writeFile).mockResolvedValue();
|
|
25
|
-
vi.mocked(fs.chmod).mockResolvedValue();
|
|
26
|
-
|
|
27
|
-
await configManager.setToken('test-token');
|
|
28
|
-
|
|
29
|
-
expect(fs.writeFile).toHaveBeenCalledWith(
|
|
30
|
-
mockConfigPath,
|
|
31
|
-
expect.stringContaining('"default"'),
|
|
32
|
-
);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should set token for specified profile', async () => {
|
|
36
|
-
vi.mocked(fs.readFile).mockRejectedValueOnce({ code: 'ENOENT' });
|
|
37
|
-
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
38
|
-
vi.mocked(fs.writeFile).mockResolvedValue();
|
|
39
|
-
vi.mocked(fs.chmod).mockResolvedValue();
|
|
40
|
-
|
|
41
|
-
await configManager.setToken('test-token', 'production');
|
|
42
|
-
|
|
43
|
-
const writeCall = vi.mocked(fs.writeFile).mock.calls[0];
|
|
44
|
-
const savedData = JSON.parse(writeCall[1] as string);
|
|
45
|
-
expect(savedData.profiles.production.token).toBe('test-token');
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe('getConfig', () => {
|
|
50
|
-
it('should return null when no config exists', async () => {
|
|
51
|
-
vi.mocked(fs.readFile).mockRejectedValueOnce({ code: 'ENOENT' });
|
|
52
|
-
|
|
53
|
-
const config = await configManager.getConfig();
|
|
54
|
-
|
|
55
|
-
expect(config).toBeNull();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('should return config for default profile', async () => {
|
|
59
|
-
const mockStore = {
|
|
60
|
-
profiles: {
|
|
61
|
-
default: {
|
|
62
|
-
token: 'test-token',
|
|
63
|
-
updatedAt: '2024-01-01T00:00:00.000Z',
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
defaultProfile: 'default',
|
|
67
|
-
};
|
|
68
|
-
vi.mocked(fs.readFile).mockResolvedValueOnce(JSON.stringify(mockStore));
|
|
69
|
-
|
|
70
|
-
const config = await configManager.getConfig();
|
|
71
|
-
|
|
72
|
-
expect(config).toEqual(mockStore.profiles.default);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should return config for specified profile', async () => {
|
|
76
|
-
const mockStore = {
|
|
77
|
-
profiles: {
|
|
78
|
-
production: {
|
|
79
|
-
token: 'prod-token',
|
|
80
|
-
updatedAt: '2024-01-01T00:00:00.000Z',
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
defaultProfile: 'default',
|
|
84
|
-
};
|
|
85
|
-
vi.mocked(fs.readFile).mockResolvedValueOnce(JSON.stringify(mockStore));
|
|
86
|
-
|
|
87
|
-
const config = await configManager.getConfig('production');
|
|
88
|
-
|
|
89
|
-
expect(config).toEqual(mockStore.profiles.production);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe('listProfiles', () => {
|
|
94
|
-
it('should return empty array when no profiles exist', async () => {
|
|
95
|
-
vi.mocked(fs.readFile).mockRejectedValueOnce({ code: 'ENOENT' });
|
|
96
|
-
|
|
97
|
-
const profiles = await configManager.listProfiles();
|
|
98
|
-
|
|
99
|
-
expect(profiles).toEqual([]);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('should return all profiles with default flag', async () => {
|
|
103
|
-
const mockStore = {
|
|
104
|
-
profiles: {
|
|
105
|
-
default: {
|
|
106
|
-
token: 'default-token',
|
|
107
|
-
updatedAt: '2024-01-01T00:00:00.000Z',
|
|
108
|
-
},
|
|
109
|
-
production: {
|
|
110
|
-
token: 'prod-token',
|
|
111
|
-
updatedAt: '2024-01-02T00:00:00.000Z',
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
defaultProfile: 'default',
|
|
115
|
-
};
|
|
116
|
-
vi.mocked(fs.readFile).mockResolvedValueOnce(JSON.stringify(mockStore));
|
|
117
|
-
|
|
118
|
-
const profiles = await configManager.listProfiles();
|
|
119
|
-
|
|
120
|
-
expect(profiles).toHaveLength(2);
|
|
121
|
-
expect(profiles.find((p) => p.name === 'default')?.isDefault).toBe(true);
|
|
122
|
-
expect(profiles.find((p) => p.name === 'production')?.isDefault).toBe(false);
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
describe('useProfile', () => {
|
|
127
|
-
it('should switch to existing profile', async () => {
|
|
128
|
-
const mockStore = {
|
|
129
|
-
profiles: {
|
|
130
|
-
default: { token: 'default-token', updatedAt: '2024-01-01T00:00:00.000Z' },
|
|
131
|
-
production: { token: 'prod-token', updatedAt: '2024-01-02T00:00:00.000Z' },
|
|
132
|
-
},
|
|
133
|
-
defaultProfile: 'default',
|
|
134
|
-
};
|
|
135
|
-
vi.mocked(fs.readFile).mockResolvedValueOnce(JSON.stringify(mockStore));
|
|
136
|
-
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
137
|
-
vi.mocked(fs.writeFile).mockResolvedValue();
|
|
138
|
-
vi.mocked(fs.chmod).mockResolvedValue();
|
|
139
|
-
|
|
140
|
-
await configManager.useProfile('production');
|
|
141
|
-
|
|
142
|
-
const writeCall = vi.mocked(fs.writeFile).mock.calls[0];
|
|
143
|
-
const savedData = JSON.parse(writeCall[1] as string);
|
|
144
|
-
expect(savedData.defaultProfile).toBe('production');
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('should throw error when profile does not exist', async () => {
|
|
148
|
-
const mockStore = {
|
|
149
|
-
profiles: {
|
|
150
|
-
default: { token: 'default-token', updatedAt: '2024-01-01T00:00:00.000Z' },
|
|
151
|
-
},
|
|
152
|
-
defaultProfile: 'default',
|
|
153
|
-
};
|
|
154
|
-
vi.mocked(fs.readFile).mockResolvedValueOnce(JSON.stringify(mockStore));
|
|
155
|
-
|
|
156
|
-
await expect(configManager.useProfile('nonexistent')).rejects.toThrow(
|
|
157
|
-
'Profile "nonexistent" does not exist',
|
|
158
|
-
);
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
describe('getCurrentProfile', () => {
|
|
163
|
-
it('should return default profile when none set', async () => {
|
|
164
|
-
vi.mocked(fs.readFile).mockRejectedValueOnce({ code: 'ENOENT' });
|
|
165
|
-
|
|
166
|
-
const profile = await configManager.getCurrentProfile();
|
|
167
|
-
|
|
168
|
-
expect(profile).toBe('default');
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('should return current profile', async () => {
|
|
172
|
-
const mockStore = {
|
|
173
|
-
profiles: {},
|
|
174
|
-
defaultProfile: 'production',
|
|
175
|
-
};
|
|
176
|
-
vi.mocked(fs.readFile).mockResolvedValueOnce(JSON.stringify(mockStore));
|
|
177
|
-
|
|
178
|
-
const profile = await configManager.getCurrentProfile();
|
|
179
|
-
|
|
180
|
-
expect(profile).toBe('production');
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
describe('clearConfig', () => {
|
|
185
|
-
it('should remove specified profile', async () => {
|
|
186
|
-
const mockStore = {
|
|
187
|
-
profiles: {
|
|
188
|
-
default: { token: 'default-token', updatedAt: '2024-01-01T00:00:00.000Z' },
|
|
189
|
-
production: { token: 'prod-token', updatedAt: '2024-01-02T00:00:00.000Z' },
|
|
190
|
-
},
|
|
191
|
-
defaultProfile: 'default',
|
|
192
|
-
};
|
|
193
|
-
vi.mocked(fs.readFile).mockResolvedValueOnce(JSON.stringify(mockStore));
|
|
194
|
-
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
195
|
-
vi.mocked(fs.writeFile).mockResolvedValue();
|
|
196
|
-
vi.mocked(fs.chmod).mockResolvedValue();
|
|
197
|
-
|
|
198
|
-
await configManager.clearConfig('production');
|
|
199
|
-
|
|
200
|
-
const writeCall = vi.mocked(fs.writeFile).mock.calls[0];
|
|
201
|
-
const savedData = JSON.parse(writeCall[1] as string);
|
|
202
|
-
expect(savedData.profiles.production).toBeUndefined();
|
|
203
|
-
expect(savedData.profiles.default).toBeDefined();
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it('should delete config file when last profile removed', async () => {
|
|
207
|
-
const mockStore = {
|
|
208
|
-
profiles: {
|
|
209
|
-
default: { token: 'default-token', updatedAt: '2024-01-01T00:00:00.000Z' },
|
|
210
|
-
},
|
|
211
|
-
defaultProfile: 'default',
|
|
212
|
-
};
|
|
213
|
-
vi.mocked(fs.readFile).mockResolvedValueOnce(JSON.stringify(mockStore));
|
|
214
|
-
vi.mocked(fs.unlink).mockResolvedValue();
|
|
215
|
-
|
|
216
|
-
await configManager.clearConfig('default');
|
|
217
|
-
|
|
218
|
-
expect(fs.unlink).toHaveBeenCalledWith(mockConfigPath);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it('should set new default when current default is removed', async () => {
|
|
222
|
-
const mockStore = {
|
|
223
|
-
profiles: {
|
|
224
|
-
default: { token: 'default-token', updatedAt: '2024-01-01T00:00:00.000Z' },
|
|
225
|
-
production: { token: 'prod-token', updatedAt: '2024-01-02T00:00:00.000Z' },
|
|
226
|
-
},
|
|
227
|
-
defaultProfile: 'default',
|
|
228
|
-
};
|
|
229
|
-
vi.mocked(fs.readFile).mockResolvedValueOnce(JSON.stringify(mockStore));
|
|
230
|
-
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
231
|
-
vi.mocked(fs.writeFile).mockResolvedValue();
|
|
232
|
-
vi.mocked(fs.chmod).mockResolvedValue();
|
|
233
|
-
|
|
234
|
-
await configManager.clearConfig('default');
|
|
235
|
-
|
|
236
|
-
const writeCall = vi.mocked(fs.writeFile).mock.calls[0];
|
|
237
|
-
const savedData = JSON.parse(writeCall[1] as string);
|
|
238
|
-
expect(savedData.defaultProfile).toBe('production');
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
describe('maskToken', () => {
|
|
243
|
-
it('should mask short tokens completely', () => {
|
|
244
|
-
const masked = configManager.maskToken('short');
|
|
245
|
-
expect(masked).toBe('****');
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it('should mask long tokens showing prefix and suffix', () => {
|
|
249
|
-
const token = 'test-1234567890-abcdefghijklmnop';
|
|
250
|
-
const masked = configManager.maskToken(token);
|
|
251
|
-
expect(masked).toBe('test-****-****-mnop');
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
describe('migration', () => {
|
|
256
|
-
it('should migrate old format to new format', async () => {
|
|
257
|
-
const oldConfig = {
|
|
258
|
-
token: 'old-token',
|
|
259
|
-
updatedAt: '2024-01-01T00:00:00.000Z',
|
|
260
|
-
};
|
|
261
|
-
vi.mocked(fs.readFile).mockResolvedValueOnce(JSON.stringify(oldConfig));
|
|
262
|
-
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
263
|
-
vi.mocked(fs.writeFile).mockResolvedValue();
|
|
264
|
-
vi.mocked(fs.chmod).mockResolvedValue();
|
|
265
|
-
|
|
266
|
-
const config = await configManager.getConfig();
|
|
267
|
-
|
|
268
|
-
expect(config).toEqual(oldConfig);
|
|
269
|
-
const writeCall = vi.mocked(fs.writeFile).mock.calls[0];
|
|
270
|
-
const savedData = JSON.parse(writeCall[1] as string);
|
|
271
|
-
expect(savedData.profiles.default).toEqual(oldConfig);
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
describe('error handling', () => {
|
|
276
|
-
it('should throw error for invalid JSON', async () => {
|
|
277
|
-
vi.mocked(fs.readFile).mockResolvedValueOnce('invalid json');
|
|
278
|
-
|
|
279
|
-
await expect(configManager.getConfig()).rejects.toThrow('Invalid config file format');
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
|
-
});
|
|
@@ -1,63 +0,0 @@
|
|
|
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
|
-
});
|