@newpeak/barista-cli 0.1.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.
- package/.eslintrc.json +23 -0
- package/.prettierrc +9 -0
- package/.sisyphus/notepads/liberica-employees/learnings.md +73 -0
- package/AGENTS.md +270 -0
- package/CONTRIBUTING.md +291 -0
- package/README.md +707 -0
- package/bin/barista +6 -0
- package/bin/barista.js +3 -0
- package/docs/ARCHITECTURE.md +184 -0
- package/docs/COMMANDS.md +352 -0
- package/docs/COMMAND_DESIGN_SPEC.md +811 -0
- package/docs/INTEGRATION_NOTES.md +270 -0
- package/docs/commands/REFERENCE.md +297 -0
- package/docs/commands/arabica/auth/index.md +296 -0
- package/docs/commands/liberica/auth/index.md +133 -0
- package/docs/commands/liberica/context/index.md +60 -0
- package/docs/commands/liberica/employees/create.md +185 -0
- package/docs/commands/liberica/employees/disable.md +138 -0
- package/docs/commands/liberica/employees/enable.md +137 -0
- package/docs/commands/liberica/employees/get.md +153 -0
- package/docs/commands/liberica/employees/list.md +168 -0
- package/docs/commands/liberica/employees/update.md +180 -0
- package/docs/commands/liberica/orgs/list.md +62 -0
- package/docs/commands/liberica/positions/list.md +61 -0
- package/docs/commands/liberica/roles/list.md +67 -0
- package/docs/commands/liberica/users/create.md +170 -0
- package/docs/commands/liberica/users/get.md +151 -0
- package/docs/commands/liberica/users/list.md +175 -0
- package/package.json +37 -0
- package/src/commands/arabica/auth/index.ts +277 -0
- package/src/commands/arabica/auth/login.ts +5 -0
- package/src/commands/arabica/auth/logout.ts +5 -0
- package/src/commands/arabica/auth/register.ts +5 -0
- package/src/commands/arabica/auth/status.ts +5 -0
- package/src/commands/arabica/index.ts +23 -0
- package/src/commands/auth.ts +107 -0
- package/src/commands/context.ts +60 -0
- package/src/commands/liberica/auth/index.ts +170 -0
- package/src/commands/liberica/context/index.ts +43 -0
- package/src/commands/liberica/employees/create.ts +275 -0
- package/src/commands/liberica/employees/delete.ts +122 -0
- package/src/commands/liberica/employees/disable.ts +97 -0
- package/src/commands/liberica/employees/enable.ts +97 -0
- package/src/commands/liberica/employees/get.ts +115 -0
- package/src/commands/liberica/employees/index.ts +23 -0
- package/src/commands/liberica/employees/list.ts +131 -0
- package/src/commands/liberica/employees/update.ts +157 -0
- package/src/commands/liberica/index.ts +36 -0
- package/src/commands/liberica/orgs/index.ts +35 -0
- package/src/commands/liberica/positions/index.ts +30 -0
- package/src/commands/liberica/roles/index.ts +59 -0
- package/src/commands/liberica/users/create.ts +132 -0
- package/src/commands/liberica/users/delete.ts +49 -0
- package/src/commands/liberica/users/disable.ts +41 -0
- package/src/commands/liberica/users/enable.ts +30 -0
- package/src/commands/liberica/users/get.ts +46 -0
- package/src/commands/liberica/users/index.ts +27 -0
- package/src/commands/liberica/users/list.ts +68 -0
- package/src/commands/liberica/users/me.ts +42 -0
- package/src/commands/liberica/users/reset-password.ts +42 -0
- package/src/commands/liberica/users/update.ts +48 -0
- package/src/core/api/client.ts +825 -0
- package/src/core/auth/token-manager.ts +183 -0
- package/src/core/config/manager.ts +164 -0
- package/src/index.ts +37 -0
- package/src/types/employee.ts +102 -0
- package/src/types/index.ts +75 -0
- package/src/types/org.ts +25 -0
- package/src/types/position.ts +24 -0
- package/src/types/user.ts +64 -0
- package/tests/unit/commands/arabica/auth.test.ts +230 -0
- package/tests/unit/commands/liberica/auth.test.ts +175 -0
- package/tests/unit/commands/liberica/context.test.ts +98 -0
- package/tests/unit/commands/liberica/employees/create.test.ts +463 -0
- package/tests/unit/commands/liberica/employees/disable.test.ts +82 -0
- package/tests/unit/commands/liberica/employees/enable.test.ts +82 -0
- package/tests/unit/commands/liberica/employees/get.test.ts +111 -0
- package/tests/unit/commands/liberica/employees/list.test.ts +294 -0
- package/tests/unit/commands/liberica/employees/update.test.ts +210 -0
- package/tests/unit/config.test.ts +141 -0
- package/tests/unit/types.test.ts +195 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
vi.mock('chalk', () => ({
|
|
4
|
+
default: {
|
|
5
|
+
bold: vi.fn((text) => text),
|
|
6
|
+
green: vi.fn((text) => text),
|
|
7
|
+
red: vi.fn((text) => text),
|
|
8
|
+
gray: vi.fn((text) => text),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
vi.mock('../../../../../src/core/config/manager.js', () => ({
|
|
15
|
+
configManager: {
|
|
16
|
+
getCurrentContext: vi.fn(() => ({
|
|
17
|
+
environment: 'dev' as const,
|
|
18
|
+
service: 'liberica' as const,
|
|
19
|
+
tenant: 'test-tenant',
|
|
20
|
+
})),
|
|
21
|
+
setTenant: vi.fn(),
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock('../../../../../src/core/auth/token-manager.js', () => ({
|
|
26
|
+
tokenManager: {
|
|
27
|
+
getToken: vi.fn(() => Promise.resolve('test-token')),
|
|
28
|
+
setToken: vi.fn(),
|
|
29
|
+
deleteToken: vi.fn(),
|
|
30
|
+
findAllTokens: vi.fn(() => []),
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
vi.mock('../../../../../src/core/api/client.js', () => ({
|
|
35
|
+
apiClient: {
|
|
36
|
+
createEmployee: vi.fn(),
|
|
37
|
+
getCodeByType: vi.fn(),
|
|
38
|
+
getOrgListName: vi.fn(),
|
|
39
|
+
listPositions: vi.fn(),
|
|
40
|
+
},
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
import { createEmployeeCreateCommand } from '../../../../../src/commands/liberica/employees/create.js';
|
|
44
|
+
import { apiClient } from '../../../../../src/core/api/client.js';
|
|
45
|
+
import { configManager } from '../../../../../src/core/config/manager.js';
|
|
46
|
+
|
|
47
|
+
const defaultContext = {
|
|
48
|
+
environment: 'dev' as const,
|
|
49
|
+
service: 'liberica' as const,
|
|
50
|
+
tenant: 'test-tenant',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
describe('createEmployeeCreateCommand', () => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
vi.clearAllMocks();
|
|
56
|
+
(configManager.getCurrentContext as ReturnType<typeof vi.fn>).mockReturnValue(defaultContext);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should create command with name "create"', () => {
|
|
60
|
+
const command = createEmployeeCreateCommand();
|
|
61
|
+
expect(command.name()).toBe('create');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should have description "Create a new employee"', () => {
|
|
65
|
+
const command = createEmployeeCreateCommand();
|
|
66
|
+
expect(command.description()).toBe('Create a new employee');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should have --name option', () => {
|
|
70
|
+
const command = createEmployeeCreateCommand();
|
|
71
|
+
const nameOption = command.options.find((opt) => opt.long === '--name');
|
|
72
|
+
expect(nameOption).toBeDefined();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should have --code option', () => {
|
|
76
|
+
const command = createEmployeeCreateCommand();
|
|
77
|
+
const codeOption = command.options.find((opt) => opt.long === '--code');
|
|
78
|
+
expect(codeOption).toBeDefined();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should have --phone option', () => {
|
|
82
|
+
const command = createEmployeeCreateCommand();
|
|
83
|
+
const phoneOption = command.options.find((opt) => opt.long === '--phone');
|
|
84
|
+
expect(phoneOption).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should have --email option', () => {
|
|
88
|
+
const command = createEmployeeCreateCommand();
|
|
89
|
+
const emailOption = command.options.find((opt) => opt.long === '--email');
|
|
90
|
+
expect(emailOption).toBeDefined();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should have --sex option', () => {
|
|
94
|
+
const command = createEmployeeCreateCommand();
|
|
95
|
+
const sexOption = command.options.find((opt) => opt.long === '--sex');
|
|
96
|
+
expect(sexOption).toBeDefined();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should have --org option', () => {
|
|
100
|
+
const command = createEmployeeCreateCommand();
|
|
101
|
+
const orgOption = command.options.find((opt) => opt.long === '--org');
|
|
102
|
+
expect(orgOption).toBeDefined();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should have --position option', () => {
|
|
106
|
+
const command = createEmployeeCreateCommand();
|
|
107
|
+
const posOption = command.options.find((opt) => opt.long === '--position');
|
|
108
|
+
expect(posOption).toBeDefined();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should have --joined-date option', () => {
|
|
112
|
+
const command = createEmployeeCreateCommand();
|
|
113
|
+
const dateOption = command.options.find((opt) => opt.long === '--joined-date');
|
|
114
|
+
expect(dateOption).toBeDefined();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should have --no option', () => {
|
|
118
|
+
const command = createEmployeeCreateCommand();
|
|
119
|
+
const noOption = command.options.find((opt) => opt.long === '--no');
|
|
120
|
+
expect(noOption).toBeDefined();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should have --dry-run option', () => {
|
|
124
|
+
const command = createEmployeeCreateCommand();
|
|
125
|
+
const dryRunOption = command.options.find((opt) => opt.long === '--dry-run');
|
|
126
|
+
expect(dryRunOption).toBeDefined();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should have --json option', () => {
|
|
130
|
+
const command = createEmployeeCreateCommand();
|
|
131
|
+
const jsonOption = command.options.find((opt) => opt.long === '--json');
|
|
132
|
+
expect(jsonOption).toBeDefined();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should have -n short option for --name', () => {
|
|
136
|
+
const command = createEmployeeCreateCommand();
|
|
137
|
+
const nameOption = command.options.find((opt) => opt.short === '-n');
|
|
138
|
+
expect(nameOption).toBeDefined();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should have -c short option for --code', () => {
|
|
142
|
+
const command = createEmployeeCreateCommand();
|
|
143
|
+
const codeOption = command.options.find((opt) => opt.short === '-c');
|
|
144
|
+
expect(codeOption).toBeDefined();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should have -p short option for --phone', () => {
|
|
148
|
+
const command = createEmployeeCreateCommand();
|
|
149
|
+
const phoneOption = command.options.find((opt) => opt.short === '-p');
|
|
150
|
+
expect(phoneOption).toBeDefined();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should have -e short option for --email', () => {
|
|
154
|
+
const command = createEmployeeCreateCommand();
|
|
155
|
+
const emailOption = command.options.find((opt) => opt.short === '-e');
|
|
156
|
+
expect(emailOption).toBeDefined();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should have -s short option for --sex', () => {
|
|
160
|
+
const command = createEmployeeCreateCommand();
|
|
161
|
+
const sexOption = command.options.find((opt) => opt.short === '-s');
|
|
162
|
+
expect(sexOption).toBeDefined();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should have -d short option for --joined-date', () => {
|
|
166
|
+
const command = createEmployeeCreateCommand();
|
|
167
|
+
const dateOption = command.options.find((opt) => opt.short === '-d');
|
|
168
|
+
expect(dateOption).toBeDefined();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('createEmployeeCreateCommand validation', () => {
|
|
173
|
+
beforeEach(() => {
|
|
174
|
+
vi.clearAllMocks();
|
|
175
|
+
(configManager.getCurrentContext as ReturnType<typeof vi.fn>).mockReturnValue(defaultContext);
|
|
176
|
+
(apiClient.getCodeByType as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
177
|
+
success: true,
|
|
178
|
+
data: 'TESTCODE001',
|
|
179
|
+
});
|
|
180
|
+
vi.spyOn(process, 'exit').mockImplementation((() => {
|
|
181
|
+
throw new Error('process.exit');
|
|
182
|
+
}) as typeof process.exit);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should validate email format', async () => {
|
|
186
|
+
const command = createEmployeeCreateCommand();
|
|
187
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
188
|
+
|
|
189
|
+
await expect(
|
|
190
|
+
command.parseAsync(['node', 'test', '--name', 'Test', '--no', 'E001', '--email', 'invalid-email'])
|
|
191
|
+
).rejects.toThrow('process.exit');
|
|
192
|
+
|
|
193
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid email format'));
|
|
194
|
+
consoleErrorSpy.mockRestore();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should validate phone format', async () => {
|
|
198
|
+
const command = createEmployeeCreateCommand();
|
|
199
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
200
|
+
|
|
201
|
+
await expect(
|
|
202
|
+
command.parseAsync(['node', 'test', '--name', 'Test', '--no', 'E001', '--phone', 'abc@123'])
|
|
203
|
+
).rejects.toThrow('process.exit');
|
|
204
|
+
|
|
205
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid phone format'));
|
|
206
|
+
consoleErrorSpy.mockRestore();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should validate date format', async () => {
|
|
210
|
+
const command = createEmployeeCreateCommand();
|
|
211
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
212
|
+
|
|
213
|
+
await expect(
|
|
214
|
+
command.parseAsync(['node', 'test', '--name', 'Test', '--no', 'E001', '--joined-date', '2024/01/15'])
|
|
215
|
+
).rejects.toThrow('process.exit');
|
|
216
|
+
|
|
217
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid date format'));
|
|
218
|
+
consoleErrorSpy.mockRestore();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should validate sex value', async () => {
|
|
222
|
+
const command = createEmployeeCreateCommand();
|
|
223
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
224
|
+
|
|
225
|
+
await expect(
|
|
226
|
+
command.parseAsync(['node', 'test', '--name', 'Test', '--no', 'E001', '--sex', 'X'])
|
|
227
|
+
).rejects.toThrow('process.exit');
|
|
228
|
+
|
|
229
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid sex value'));
|
|
230
|
+
consoleErrorSpy.mockRestore();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('createEmployeeCreateCommand dry-run', () => {
|
|
235
|
+
beforeEach(() => {
|
|
236
|
+
vi.clearAllMocks();
|
|
237
|
+
(configManager.getCurrentContext as ReturnType<typeof vi.fn>).mockReturnValue(defaultContext);
|
|
238
|
+
(apiClient.getCodeByType as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
239
|
+
success: true,
|
|
240
|
+
data: 'TESTCODE001',
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should not call apiClient.createEmployee in dry-run mode', async () => {
|
|
245
|
+
const command = createEmployeeCreateCommand();
|
|
246
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
247
|
+
|
|
248
|
+
await command.parseAsync(['node', 'test', '--name', 'Test User', '--no', 'E001', '--dry-run']);
|
|
249
|
+
|
|
250
|
+
expect(apiClient.createEmployee).not.toHaveBeenCalled();
|
|
251
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('Dry-Run'));
|
|
252
|
+
consoleLogSpy.mockRestore();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should output JSON in dry-run mode with --json', async () => {
|
|
256
|
+
const command = createEmployeeCreateCommand();
|
|
257
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
258
|
+
|
|
259
|
+
await command.parseAsync(['node', 'test', '--name', 'Test User', '--no', 'E001', '--dry-run', '--json']);
|
|
260
|
+
|
|
261
|
+
expect(apiClient.createEmployee).not.toHaveBeenCalled();
|
|
262
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('"dryRun":true'));
|
|
263
|
+
consoleLogSpy.mockRestore();
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe('createEmployeeCreateCommand API interaction', () => {
|
|
268
|
+
beforeEach(() => {
|
|
269
|
+
vi.clearAllMocks();
|
|
270
|
+
(configManager.getCurrentContext as ReturnType<typeof vi.fn>).mockReturnValue(defaultContext);
|
|
271
|
+
(apiClient.getCodeByType as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
272
|
+
success: true,
|
|
273
|
+
data: 'TESTCODE001',
|
|
274
|
+
});
|
|
275
|
+
(apiClient.getOrgListName as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
276
|
+
success: true,
|
|
277
|
+
data: [
|
|
278
|
+
{ id: 100, name: '人力资源部' },
|
|
279
|
+
{ id: 101, name: '销售部' },
|
|
280
|
+
],
|
|
281
|
+
});
|
|
282
|
+
(apiClient.listPositions as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
283
|
+
success: true,
|
|
284
|
+
data: [
|
|
285
|
+
{ id: 'MANAGER', name: '经理' },
|
|
286
|
+
{ id: 'STAFF', name: '员工' },
|
|
287
|
+
],
|
|
288
|
+
});
|
|
289
|
+
vi.spyOn(process, 'exit').mockImplementation((() => {
|
|
290
|
+
throw new Error('process.exit');
|
|
291
|
+
}) as typeof process.exit);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should call apiClient.createEmployee with correct data on happy path', async () => {
|
|
295
|
+
(apiClient.createEmployee as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
296
|
+
success: true,
|
|
297
|
+
data: {
|
|
298
|
+
employeeId: 1,
|
|
299
|
+
employeeName: 'Test User',
|
|
300
|
+
employeeNo: 'E001',
|
|
301
|
+
statusFlag: 1 as const,
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const command = createEmployeeCreateCommand();
|
|
306
|
+
|
|
307
|
+
await command.parseAsync([
|
|
308
|
+
'node', 'test',
|
|
309
|
+
'--name', 'Test User',
|
|
310
|
+
'--no', 'E001',
|
|
311
|
+
'--code', 'CODE001',
|
|
312
|
+
'--phone', '13800138000',
|
|
313
|
+
'--email', 'test@example.com',
|
|
314
|
+
'--sex', 'M',
|
|
315
|
+
'--org', '人力资源部',
|
|
316
|
+
'--position', '经理',
|
|
317
|
+
'--joined-date', '2024-01-15',
|
|
318
|
+
]);
|
|
319
|
+
|
|
320
|
+
expect(apiClient.createEmployee).toHaveBeenCalledWith(
|
|
321
|
+
'dev',
|
|
322
|
+
'test-tenant',
|
|
323
|
+
expect.objectContaining({
|
|
324
|
+
employeeName: 'Test User',
|
|
325
|
+
employeeNo: 'E001',
|
|
326
|
+
employeeCode: 'CODE001',
|
|
327
|
+
employeePhone: '13800138000',
|
|
328
|
+
employeeEmail: 'test@example.com',
|
|
329
|
+
employeeSex: 'M',
|
|
330
|
+
organizationId: 100,
|
|
331
|
+
positionId: 'MANAGER',
|
|
332
|
+
employeeJoinedDate: '2024-01-15',
|
|
333
|
+
})
|
|
334
|
+
);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('should call apiClient.createEmployee with minimal fields', async () => {
|
|
338
|
+
(apiClient.createEmployee as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
339
|
+
success: true,
|
|
340
|
+
data: {
|
|
341
|
+
employeeId: 2,
|
|
342
|
+
employeeName: 'Minimal User',
|
|
343
|
+
employeeNo: 'E002',
|
|
344
|
+
statusFlag: 1 as const,
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const command = createEmployeeCreateCommand();
|
|
349
|
+
|
|
350
|
+
await command.parseAsync(['node', 'test', '--name', 'Minimal User', '--no', 'E002']);
|
|
351
|
+
|
|
352
|
+
expect(apiClient.createEmployee).toHaveBeenCalledWith(
|
|
353
|
+
'dev',
|
|
354
|
+
'test-tenant',
|
|
355
|
+
expect.objectContaining({
|
|
356
|
+
employeeName: 'Minimal User',
|
|
357
|
+
employeeNo: 'E002',
|
|
358
|
+
})
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('should handle API error and exit with code 1', async () => {
|
|
363
|
+
(apiClient.createEmployee as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
364
|
+
success: false,
|
|
365
|
+
error: {
|
|
366
|
+
code: '01001150003',
|
|
367
|
+
message: '职员编码规则不能为空',
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const command = createEmployeeCreateCommand();
|
|
372
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
373
|
+
|
|
374
|
+
await expect(
|
|
375
|
+
command.parseAsync(['node', 'test', '--name', 'Test', '--no', 'E001'])
|
|
376
|
+
).rejects.toThrow('process.exit');
|
|
377
|
+
|
|
378
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('职员编码规则不能为空'));
|
|
379
|
+
consoleErrorSpy.mockRestore();
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('should handle API network error', async () => {
|
|
383
|
+
(apiClient.createEmployee as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
384
|
+
success: false,
|
|
385
|
+
error: {
|
|
386
|
+
code: 'NETWORK_ERROR',
|
|
387
|
+
message: 'Network error occurred',
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const command = createEmployeeCreateCommand();
|
|
392
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
393
|
+
|
|
394
|
+
await expect(
|
|
395
|
+
command.parseAsync(['node', 'test', '--name', 'Test', '--no', 'E001'])
|
|
396
|
+
).rejects.toThrow('process.exit');
|
|
397
|
+
|
|
398
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Network error occurred'));
|
|
399
|
+
consoleErrorSpy.mockRestore();
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('should output JSON on API success', async () => {
|
|
403
|
+
(apiClient.createEmployee as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
404
|
+
success: true,
|
|
405
|
+
data: {
|
|
406
|
+
employeeId: 1,
|
|
407
|
+
employeeName: 'Test User',
|
|
408
|
+
employeeNo: 'E001',
|
|
409
|
+
statusFlag: 1 as const,
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
const command = createEmployeeCreateCommand();
|
|
414
|
+
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
415
|
+
|
|
416
|
+
await command.parseAsync(['node', 'test', '--name', 'Test User', '--no', 'E001', '--json']);
|
|
417
|
+
|
|
418
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('"success":true'));
|
|
419
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('"employeeId":1'));
|
|
420
|
+
consoleLogSpy.mockRestore();
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('should error when organization name not found', async () => {
|
|
424
|
+
const command = createEmployeeCreateCommand();
|
|
425
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
426
|
+
|
|
427
|
+
await expect(
|
|
428
|
+
command.parseAsync(['node', 'test', '--name', 'Test', '--no', 'E001', '--org', '不存在的部门'])
|
|
429
|
+
).rejects.toThrow('process.exit');
|
|
430
|
+
|
|
431
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Organization '不存在的部门' not found"));
|
|
432
|
+
consoleErrorSpy.mockRestore();
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('should error when position name not found', async () => {
|
|
436
|
+
const command = createEmployeeCreateCommand();
|
|
437
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
438
|
+
|
|
439
|
+
await expect(
|
|
440
|
+
command.parseAsync(['node', 'test', '--name', 'Test', '--no', 'E001', '--position', '不存在的岗位'])
|
|
441
|
+
).rejects.toThrow('process.exit');
|
|
442
|
+
|
|
443
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Position '不存在的岗位' not found"));
|
|
444
|
+
consoleErrorSpy.mockRestore();
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
describe('createEmployeeCreateCommand interactive prompts', () => {
|
|
449
|
+
beforeEach(() => {
|
|
450
|
+
vi.clearAllMocks();
|
|
451
|
+
(configManager.getCurrentContext as ReturnType<typeof vi.fn>).mockReturnValue(defaultContext);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it('should have prompt behavior for missing name', () => {
|
|
455
|
+
const command = createEmployeeCreateCommand();
|
|
456
|
+
expect(command).toBeDefined();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('should have prompt behavior for missing no', () => {
|
|
460
|
+
const command = createEmployeeCreateCommand();
|
|
461
|
+
expect(command).toBeDefined();
|
|
462
|
+
});
|
|
463
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
vi.mock('chalk', () => ({
|
|
4
|
+
default: {
|
|
5
|
+
bold: vi.fn((text) => text),
|
|
6
|
+
green: vi.fn((text) => text),
|
|
7
|
+
red: vi.fn((text) => text),
|
|
8
|
+
gray: vi.fn((text) => text),
|
|
9
|
+
cyan: vi.fn((text) => text),
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock('inquirer', () => ({
|
|
14
|
+
default: {
|
|
15
|
+
prompt: vi.fn(),
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
vi.mock('../../../../../src/core/config/manager.js', () => ({
|
|
20
|
+
configManager: {
|
|
21
|
+
getCurrentContext: vi.fn(() => ({
|
|
22
|
+
environment: 'dev' as const,
|
|
23
|
+
service: 'liberica' as const,
|
|
24
|
+
tenant: 'test-tenant',
|
|
25
|
+
})),
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
vi.mock('../../../../../src/core/api/client.js', () => ({
|
|
30
|
+
apiClient: {
|
|
31
|
+
getEmployee: vi.fn(),
|
|
32
|
+
disableEmployee: vi.fn(),
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
import { createEmployeeDisableCommand } from '../../../../../src/commands/liberica/employees/disable.js';
|
|
37
|
+
|
|
38
|
+
describe('createEmployeeDisableCommand', () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
vi.clearAllMocks();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should create command with name "disable"', () => {
|
|
44
|
+
const command = createEmployeeDisableCommand();
|
|
45
|
+
expect(command.name()).toBe('disable');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should have correct description', () => {
|
|
49
|
+
const command = createEmployeeDisableCommand();
|
|
50
|
+
expect(command.description()).toBe('Disable (deactivate) an employee account');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('API integration', () => {
|
|
54
|
+
it('should call apiClient.getEmployee to fetch employee details', async () => {
|
|
55
|
+
const { apiClient } = await import('../../../../../src/core/api/client.js');
|
|
56
|
+
expect(apiClient.getEmployee).toBeDefined();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should call apiClient.disableEmployee to disable employee', async () => {
|
|
60
|
+
const { apiClient } = await import('../../../../../src/core/api/client.js');
|
|
61
|
+
expect(apiClient.disableEmployee).toBeDefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should handle employee not found error', async () => {
|
|
65
|
+
const { apiClient } = await import('../../../../../src/core/api/client.js');
|
|
66
|
+
(apiClient.getEmployee as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
67
|
+
success: false,
|
|
68
|
+
error: { code: '01001150001', message: 'Employee not found' },
|
|
69
|
+
});
|
|
70
|
+
expect(apiClient.getEmployee).toBeDefined();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should handle already disabled employee (idempotent)', async () => {
|
|
74
|
+
const { apiClient } = await import('../../../../../src/core/api/client.js');
|
|
75
|
+
(apiClient.disableEmployee as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
76
|
+
success: false,
|
|
77
|
+
error: { code: '01001150001', message: 'Employee not found' },
|
|
78
|
+
});
|
|
79
|
+
expect(apiClient.disableEmployee).toBeDefined();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
vi.mock('chalk', () => ({
|
|
4
|
+
default: {
|
|
5
|
+
bold: vi.fn((text) => text),
|
|
6
|
+
green: vi.fn((text) => text),
|
|
7
|
+
red: vi.fn((text) => text),
|
|
8
|
+
gray: vi.fn((text) => text),
|
|
9
|
+
cyan: vi.fn((text) => text),
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock('inquirer', () => ({
|
|
14
|
+
default: {
|
|
15
|
+
prompt: vi.fn(),
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
vi.mock('../../../../../src/core/config/manager.js', () => ({
|
|
20
|
+
configManager: {
|
|
21
|
+
getCurrentContext: vi.fn(() => ({
|
|
22
|
+
environment: 'dev' as const,
|
|
23
|
+
service: 'liberica' as const,
|
|
24
|
+
tenant: 'test-tenant',
|
|
25
|
+
})),
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
vi.mock('../../../../../src/core/api/client.js', () => ({
|
|
30
|
+
apiClient: {
|
|
31
|
+
getEmployee: vi.fn(),
|
|
32
|
+
enableEmployee: vi.fn(),
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
import { createEmployeeEnableCommand } from '../../../../../src/commands/liberica/employees/enable.js';
|
|
37
|
+
|
|
38
|
+
describe('createEmployeeEnableCommand', () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
vi.clearAllMocks();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should create command with name "enable"', () => {
|
|
44
|
+
const command = createEmployeeEnableCommand();
|
|
45
|
+
expect(command.name()).toBe('enable');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should have correct description', () => {
|
|
49
|
+
const command = createEmployeeEnableCommand();
|
|
50
|
+
expect(command.description()).toBe('Enable (activate) an employee account');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('API integration', () => {
|
|
54
|
+
it('should call apiClient.getEmployee to fetch employee details', async () => {
|
|
55
|
+
const { apiClient } = await import('../../../../../src/core/api/client.js');
|
|
56
|
+
expect(apiClient.getEmployee).toBeDefined();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should call apiClient.enableEmployee to enable employee', async () => {
|
|
60
|
+
const { apiClient } = await import('../../../../../src/core/api/client.js');
|
|
61
|
+
expect(apiClient.enableEmployee).toBeDefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should handle employee not found error', async () => {
|
|
65
|
+
const { apiClient } = await import('../../../../../src/core/api/client.js');
|
|
66
|
+
(apiClient.getEmployee as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
67
|
+
success: false,
|
|
68
|
+
error: { code: '01001150001', message: 'Employee not found' },
|
|
69
|
+
});
|
|
70
|
+
expect(apiClient.getEmployee).toBeDefined();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should handle already enabled employee (idempotent)', async () => {
|
|
74
|
+
const { apiClient } = await import('../../../../../src/core/api/client.js');
|
|
75
|
+
(apiClient.enableEmployee as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
76
|
+
success: false,
|
|
77
|
+
error: { code: '01001150001', message: 'Employee not found' },
|
|
78
|
+
});
|
|
79
|
+
expect(apiClient.enableEmployee).toBeDefined();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|