shawnxixi-cli 1.1.4 → 1.2.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/dist/commands/biz/calendar.js +2 -2
- package/dist/commands/biz/calendar.js.map +1 -1
- package/dist/commands/biz/finance.js +4 -4
- package/dist/commands/biz/finance.js.map +1 -1
- package/dist/commands/biz/health.js +3 -3
- package/dist/commands/biz/health.js.map +1 -1
- package/dist/commands/biz/item.js +4 -4
- package/dist/commands/biz/item.js.map +1 -1
- package/dist/commands/biz/record.js +2 -2
- package/dist/commands/biz/record.js.map +1 -1
- package/dist/commands/biz/task.js +4 -4
- package/dist/commands/biz/task.js.map +1 -1
- package/dist/commands/biz/todo.js +8 -8
- package/dist/commands/biz/todo.js.map +1 -1
- package/dist/utils/output.d.ts +4 -4
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +1 -1
- package/dist/utils/output.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/biz/calendar.ts +10 -10
- package/src/commands/biz/finance.ts +11 -11
- package/src/commands/biz/health.ts +9 -9
- package/src/commands/biz/item.ts +8 -8
- package/src/commands/biz/record.ts +7 -7
- package/src/commands/biz/task.ts +15 -15
- package/src/commands/biz/todo.ts +24 -24
- package/src/utils/output.ts +6 -6
- package/tests/commands/calendar.test.ts +124 -0
- package/tests/commands/health.test.ts +160 -0
- package/tests/commands/record.test.ts +134 -0
- package/tests/commands/task.test.ts +180 -0
package/src/commands/biz/item.ts
CHANGED
|
@@ -35,9 +35,9 @@ export const itemCommands = {
|
|
|
35
35
|
const result = await apiService.createItem(name, options.category, options.location, options.expire, options.quantity, options.unit, options.brand, options.barcode, options.price, options.supplier, options.purchaseDate);
|
|
36
36
|
loading.succeed('物品创建成功');
|
|
37
37
|
output(result);
|
|
38
|
-
} catch (error:
|
|
38
|
+
} catch (error: unknown) {
|
|
39
39
|
loading.fail('创建物品失败');
|
|
40
|
-
outputError(error.message);
|
|
40
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
41
41
|
process.exit(1);
|
|
42
42
|
}
|
|
43
43
|
},
|
|
@@ -65,9 +65,9 @@ export const itemCommands = {
|
|
|
65
65
|
} else {
|
|
66
66
|
output({ message: '暂无物品' });
|
|
67
67
|
}
|
|
68
|
-
} catch (error:
|
|
68
|
+
} catch (error: unknown) {
|
|
69
69
|
loading.fail('加载物品列表失败');
|
|
70
|
-
outputError(error.message);
|
|
70
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
71
71
|
process.exit(1);
|
|
72
72
|
}
|
|
73
73
|
},
|
|
@@ -96,9 +96,9 @@ export const itemCommands = {
|
|
|
96
96
|
} else {
|
|
97
97
|
output({ message: '暂无即将过期的物品' });
|
|
98
98
|
}
|
|
99
|
-
} catch (error:
|
|
99
|
+
} catch (error: unknown) {
|
|
100
100
|
loading.fail('查找即将过期物品失败');
|
|
101
|
-
outputError(error.message);
|
|
101
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
102
102
|
process.exit(1);
|
|
103
103
|
}
|
|
104
104
|
},
|
|
@@ -121,9 +121,9 @@ export const itemCommands = {
|
|
|
121
121
|
const result = await apiService.getItemStats();
|
|
122
122
|
loading.succeed('统计完成');
|
|
123
123
|
output(result);
|
|
124
|
-
} catch (error:
|
|
124
|
+
} catch (error: unknown) {
|
|
125
125
|
loading.fail('统计失败');
|
|
126
|
-
outputError(error.message);
|
|
126
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
127
127
|
process.exit(1);
|
|
128
128
|
}
|
|
129
129
|
},
|
|
@@ -27,35 +27,35 @@ export const recordCommands = {
|
|
|
27
27
|
const result = await apiService.createRecord(title, content, options.type, options.tags);
|
|
28
28
|
loading.succeed('记录创建成功');
|
|
29
29
|
output(result);
|
|
30
|
-
} catch (error:
|
|
30
|
+
} catch (error: unknown) {
|
|
31
31
|
loading.fail('创建记录失败');
|
|
32
|
-
outputError(error.message);
|
|
32
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
33
33
|
process.exit(1);
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
|
|
37
37
|
list: async (options: { type?: string; tags?: string } = {}) => {
|
|
38
38
|
const loading = createLoading('加载记录列表...');
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
try {
|
|
41
41
|
if (isDryRun()) {
|
|
42
42
|
loading.stop();
|
|
43
43
|
dryRunLog('列出记录', options);
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
loading.start();
|
|
48
48
|
const result = await apiService.listRecords(options.type, options.tags);
|
|
49
49
|
loading.succeed('记录列表加载成功');
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
if (result.data && result.data.length > 0) {
|
|
52
52
|
output(result.data);
|
|
53
53
|
} else {
|
|
54
54
|
output({ message: '暂无记录' });
|
|
55
55
|
}
|
|
56
|
-
} catch (error:
|
|
56
|
+
} catch (error: unknown) {
|
|
57
57
|
loading.fail('加载记录列表失败');
|
|
58
|
-
outputError(error.message);
|
|
58
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
59
59
|
process.exit(1);
|
|
60
60
|
}
|
|
61
61
|
},
|
package/src/commands/biz/task.ts
CHANGED
|
@@ -25,15 +25,15 @@ export const taskCommands = {
|
|
|
25
25
|
loading.start();
|
|
26
26
|
const result = await apiService.listTasks(options.status, options.source);
|
|
27
27
|
loading.succeed('任务列表加载成功');
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
if (result.data && result.data.length > 0) {
|
|
30
30
|
output(result.data);
|
|
31
31
|
} else {
|
|
32
32
|
output({ message: '暂无任务' });
|
|
33
33
|
}
|
|
34
|
-
} catch (error:
|
|
34
|
+
} catch (error: unknown) {
|
|
35
35
|
loading.fail('加载任务列表失败');
|
|
36
|
-
outputError(error.message);
|
|
36
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
37
37
|
process.exit(1);
|
|
38
38
|
}
|
|
39
39
|
},
|
|
@@ -44,21 +44,21 @@ export const taskCommands = {
|
|
|
44
44
|
*/
|
|
45
45
|
create: async (title: string, options: { source?: string; openclawId?: string; priority?: string } = {}) => {
|
|
46
46
|
const loading = createLoading('创建任务...');
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
try {
|
|
49
49
|
if (isDryRun()) {
|
|
50
50
|
loading.stop();
|
|
51
51
|
dryRunLog('创建任务', { title, ...options });
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
loading.start();
|
|
56
56
|
const result = await apiService.createTask(title, options.source, options.openclawId, options.priority);
|
|
57
57
|
loading.succeed('任务创建成功');
|
|
58
58
|
output(result);
|
|
59
|
-
} catch (error:
|
|
59
|
+
} catch (error: unknown) {
|
|
60
60
|
loading.fail('创建任务失败');
|
|
61
|
-
outputError(error.message);
|
|
61
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
62
62
|
process.exit(1);
|
|
63
63
|
}
|
|
64
64
|
},
|
|
@@ -69,14 +69,14 @@ export const taskCommands = {
|
|
|
69
69
|
*/
|
|
70
70
|
update: async (id: number, options: { status?: string; priority?: string } = {}) => {
|
|
71
71
|
const loading = createLoading('更新任务...');
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
try {
|
|
74
74
|
if (isDryRun()) {
|
|
75
75
|
loading.stop();
|
|
76
76
|
dryRunLog('更新任务', { id, ...options });
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
loading.start();
|
|
81
81
|
const updates: Partial<{status: string; priority: string}> = {};
|
|
82
82
|
if (options.status) updates.status = options.status;
|
|
@@ -84,9 +84,9 @@ export const taskCommands = {
|
|
|
84
84
|
const result = await apiService.updateTask(id, updates);
|
|
85
85
|
loading.succeed('任务更新成功');
|
|
86
86
|
output(result);
|
|
87
|
-
} catch (error:
|
|
87
|
+
} catch (error: unknown) {
|
|
88
88
|
loading.fail('更新任务失败');
|
|
89
|
-
outputError(error.message);
|
|
89
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
90
90
|
process.exit(1);
|
|
91
91
|
}
|
|
92
92
|
},
|
|
@@ -97,21 +97,21 @@ export const taskCommands = {
|
|
|
97
97
|
*/
|
|
98
98
|
delete: async (id: number) => {
|
|
99
99
|
const loading = createLoading('删除任务...');
|
|
100
|
-
|
|
100
|
+
|
|
101
101
|
try {
|
|
102
102
|
if (isDryRun()) {
|
|
103
103
|
loading.stop();
|
|
104
104
|
dryRunLog('删除任务', { id });
|
|
105
105
|
return;
|
|
106
106
|
}
|
|
107
|
-
|
|
107
|
+
|
|
108
108
|
loading.start();
|
|
109
109
|
const result = await apiService.deleteTask(id);
|
|
110
110
|
loading.succeed('任务删除成功');
|
|
111
111
|
output(result);
|
|
112
|
-
} catch (error:
|
|
112
|
+
} catch (error: unknown) {
|
|
113
113
|
loading.fail('删除任务失败');
|
|
114
|
-
outputError(error.message);
|
|
114
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
115
115
|
process.exit(1);
|
|
116
116
|
}
|
|
117
117
|
},
|
package/src/commands/biz/todo.ts
CHANGED
|
@@ -39,9 +39,9 @@ export const todoCommands = {
|
|
|
39
39
|
const result = await apiService.createTodo(title, options.due, options.priority, options.tags, options.repeat, options.endDate, options.parentId);
|
|
40
40
|
loading.succeed('待办创建成功');
|
|
41
41
|
output(result);
|
|
42
|
-
} catch (error:
|
|
42
|
+
} catch (error: unknown) {
|
|
43
43
|
loading.fail('创建待办失败');
|
|
44
|
-
outputError(error.message);
|
|
44
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
45
45
|
process.exit(1);
|
|
46
46
|
}
|
|
47
47
|
},
|
|
@@ -52,26 +52,26 @@ export const todoCommands = {
|
|
|
52
52
|
*/
|
|
53
53
|
list: async (options: { status?: string; priority?: string; tags?: string } = {}) => {
|
|
54
54
|
const loading = createLoading('加载待办列表...');
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
try {
|
|
57
57
|
if (isDryRun()) {
|
|
58
58
|
loading.stop();
|
|
59
59
|
dryRunLog('列出待办', options);
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
loading.start();
|
|
64
64
|
const result = await apiService.listTodos(options.status, options.priority, options.tags);
|
|
65
65
|
loading.succeed('待办列表加载成功');
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
if (result.data && result.data.length > 0) {
|
|
68
68
|
output(result.data);
|
|
69
69
|
} else {
|
|
70
70
|
output({ message: '暂无待办' });
|
|
71
71
|
}
|
|
72
|
-
} catch (error:
|
|
72
|
+
} catch (error: unknown) {
|
|
73
73
|
loading.fail('加载待办列表失败');
|
|
74
|
-
outputError(error.message);
|
|
74
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
75
75
|
process.exit(1);
|
|
76
76
|
}
|
|
77
77
|
},
|
|
@@ -82,14 +82,14 @@ export const todoCommands = {
|
|
|
82
82
|
*/
|
|
83
83
|
update: async (id: number, options: { title?: string; status?: string; priority?: string; due?: string; tags?: string } = {}) => {
|
|
84
84
|
const loading = createLoading('更新待办...');
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
try {
|
|
87
87
|
if (isDryRun()) {
|
|
88
88
|
loading.stop();
|
|
89
89
|
dryRunLog('更新待办', { id, ...options });
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
loading.start();
|
|
94
94
|
const updates: Partial<{title: string; status: string; priority: string; dueDate: string; tags: string}> = {};
|
|
95
95
|
if (options.title) updates.title = options.title;
|
|
@@ -97,13 +97,13 @@ export const todoCommands = {
|
|
|
97
97
|
if (options.priority) updates.priority = options.priority;
|
|
98
98
|
if (options.due) updates.dueDate = options.due;
|
|
99
99
|
if (options.tags) updates.tags = options.tags;
|
|
100
|
-
|
|
100
|
+
|
|
101
101
|
const result = await apiService.updateTodo(id, updates);
|
|
102
102
|
loading.succeed('待办更新成功');
|
|
103
103
|
output(result);
|
|
104
|
-
} catch (error:
|
|
104
|
+
} catch (error: unknown) {
|
|
105
105
|
loading.fail('更新待办失败');
|
|
106
|
-
outputError(error.message);
|
|
106
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
107
107
|
process.exit(1);
|
|
108
108
|
}
|
|
109
109
|
},
|
|
@@ -114,21 +114,21 @@ export const todoCommands = {
|
|
|
114
114
|
*/
|
|
115
115
|
done: async (id: number) => {
|
|
116
116
|
const loading = createLoading('标记待办完成...');
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
try {
|
|
119
119
|
if (isDryRun()) {
|
|
120
120
|
loading.stop();
|
|
121
121
|
dryRunLog('标记待办完成', { id });
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
|
-
|
|
124
|
+
|
|
125
125
|
loading.start();
|
|
126
126
|
const result = await apiService.updateTodo(id, { status: 'done' });
|
|
127
127
|
loading.succeed('待办已完成');
|
|
128
128
|
output(result);
|
|
129
|
-
} catch (error:
|
|
129
|
+
} catch (error: unknown) {
|
|
130
130
|
loading.fail('标记待办完成失败');
|
|
131
|
-
outputError(error.message);
|
|
131
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
132
132
|
process.exit(1);
|
|
133
133
|
}
|
|
134
134
|
},
|
|
@@ -151,9 +151,9 @@ export const todoCommands = {
|
|
|
151
151
|
const result = await apiService.deleteTodo(id);
|
|
152
152
|
loading.succeed('待办已删除');
|
|
153
153
|
output(result);
|
|
154
|
-
} catch (error:
|
|
154
|
+
} catch (error: unknown) {
|
|
155
155
|
loading.fail('删除待办失败');
|
|
156
|
-
outputError(error.message);
|
|
156
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
157
157
|
process.exit(1);
|
|
158
158
|
}
|
|
159
159
|
},
|
|
@@ -176,9 +176,9 @@ export const todoCommands = {
|
|
|
176
176
|
const result = await apiService.repeatTodo(id);
|
|
177
177
|
loading.succeed('下次 occurrence 已生成');
|
|
178
178
|
output(result);
|
|
179
|
-
} catch (error:
|
|
179
|
+
} catch (error: unknown) {
|
|
180
180
|
loading.fail('处理重复待办失败');
|
|
181
|
-
outputError(error.message);
|
|
181
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
182
182
|
process.exit(1);
|
|
183
183
|
}
|
|
184
184
|
},
|
|
@@ -207,9 +207,9 @@ export const todoCommands = {
|
|
|
207
207
|
const result = await apiService.createTodo(options.title, undefined, undefined, undefined, undefined, undefined, String(parentId));
|
|
208
208
|
loading.succeed('子待办创建成功');
|
|
209
209
|
output(result);
|
|
210
|
-
} catch (error:
|
|
210
|
+
} catch (error: unknown) {
|
|
211
211
|
loading.fail('创建子待办失败');
|
|
212
|
-
outputError(error.message);
|
|
212
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
213
213
|
process.exit(1);
|
|
214
214
|
}
|
|
215
215
|
},
|
|
@@ -237,9 +237,9 @@ export const todoCommands = {
|
|
|
237
237
|
} else {
|
|
238
238
|
output({ message: '暂无子待办' });
|
|
239
239
|
}
|
|
240
|
-
} catch (error:
|
|
240
|
+
} catch (error: unknown) {
|
|
241
241
|
loading.fail('加载子待办列表失败');
|
|
242
|
-
outputError(error.message);
|
|
242
|
+
outputError(error instanceof Error ? error.message : String(error));
|
|
243
243
|
process.exit(1);
|
|
244
244
|
}
|
|
245
245
|
},
|
package/src/utils/output.ts
CHANGED
|
@@ -17,9 +17,9 @@ export function setGlobalFlags(args: { json?: boolean; quiet?: boolean; dryRun?:
|
|
|
17
17
|
/**
|
|
18
18
|
* 统一输出函数
|
|
19
19
|
*/
|
|
20
|
-
export function output(data:
|
|
20
|
+
export function output(data: unknown, options: { quiet?: boolean } = {}): void {
|
|
21
21
|
if (globalQuietFlag || options.quiet) return;
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
if (globalJsonFlag) {
|
|
24
24
|
console.log(JSON.stringify(data, null, 2));
|
|
25
25
|
} else {
|
|
@@ -30,16 +30,16 @@ export function output(data: any, options: { quiet?: boolean } = {}): void {
|
|
|
30
30
|
/**
|
|
31
31
|
* 错误输出
|
|
32
32
|
*/
|
|
33
|
-
export function outputError(message: string, ...args:
|
|
33
|
+
export function outputError(message: string, ...args: unknown[]): void {
|
|
34
34
|
if (!globalQuietFlag) {
|
|
35
|
-
console.error(`\x1b[31m
|
|
35
|
+
console.error(`\x1b[31m[ERROR]\x1b[0m ${message}`, ...args);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* 成功输出
|
|
41
41
|
*/
|
|
42
|
-
export function outputSuccess(message: string, ...args:
|
|
42
|
+
export function outputSuccess(message: string, ...args: unknown[]): void {
|
|
43
43
|
if (!globalQuietFlag) {
|
|
44
44
|
console.log(`\x1b[32m✔ ${message}\x1b[0m`, ...args);
|
|
45
45
|
}
|
|
@@ -116,7 +116,7 @@ export function isDryRun(): boolean {
|
|
|
116
116
|
return globalDryRunFlag;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
export function dryRunLog(action: string, details?:
|
|
119
|
+
export function dryRunLog(action: string, details?: unknown): void {
|
|
120
120
|
if (globalDryRunFlag) {
|
|
121
121
|
console.log(`\x1b[33m[DRY-RUN]\x1b[0m ${action}`);
|
|
122
122
|
if (details && !globalQuietFlag) {
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar Command Tests
|
|
3
|
+
* Comprehensive CLI integration tests for biz calendar commands
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { calendarCommands } from '../../src/commands/biz/calendar';
|
|
7
|
+
import { setGlobalFlags } from '../../src/utils/output';
|
|
8
|
+
|
|
9
|
+
// Mock apiService
|
|
10
|
+
jest.mock('../../src/services/api', () => ({
|
|
11
|
+
apiService: {
|
|
12
|
+
createCalendarEvent: jest.fn().mockResolvedValue({ id: 1, title: '测试日程' }),
|
|
13
|
+
listCalendarEvents: jest.fn().mockResolvedValue({ data: [{ id: 1, title: '测试日程' }] }),
|
|
14
|
+
},
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
import { apiService } from '../../src/services/api';
|
|
18
|
+
|
|
19
|
+
// Mock process.exit to prevent test from exiting
|
|
20
|
+
const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
21
|
+
|
|
22
|
+
// Suppress console output during tests
|
|
23
|
+
beforeAll(() => {
|
|
24
|
+
setGlobalFlags({ quiet: true });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterAll(() => {
|
|
28
|
+
setGlobalFlags({ quiet: false });
|
|
29
|
+
mockExit.mockRestore();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('biz calendar commands', () => {
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
jest.clearAllMocks();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('create', () => {
|
|
38
|
+
it('should call apiService.createCalendarEvent with title and required options', async () => {
|
|
39
|
+
await calendarCommands.create('测试日程', { from: '2026-04-12', to: '2026-04-13' });
|
|
40
|
+
expect(apiService.createCalendarEvent).toHaveBeenCalledWith(
|
|
41
|
+
'测试日程',
|
|
42
|
+
'2026-04-12',
|
|
43
|
+
'2026-04-13',
|
|
44
|
+
undefined,
|
|
45
|
+
undefined,
|
|
46
|
+
undefined
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should call apiService.createCalendarEvent with all options', async () => {
|
|
51
|
+
await calendarCommands.create('测试日程', {
|
|
52
|
+
from: '2026-04-12 10:00',
|
|
53
|
+
to: '2026-04-12 11:00',
|
|
54
|
+
desc: '会议讨论',
|
|
55
|
+
location: '会议室A',
|
|
56
|
+
color: '#ff0000',
|
|
57
|
+
});
|
|
58
|
+
expect(apiService.createCalendarEvent).toHaveBeenCalledWith(
|
|
59
|
+
'测试日程',
|
|
60
|
+
'2026-04-12 10:00',
|
|
61
|
+
'2026-04-12 11:00',
|
|
62
|
+
'会议讨论',
|
|
63
|
+
'会议室A',
|
|
64
|
+
'#ff0000'
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should exit with error when from is missing', async () => {
|
|
69
|
+
await calendarCommands.create('测试日程', { to: '2026-04-13' });
|
|
70
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should exit with error when to is missing', async () => {
|
|
74
|
+
await calendarCommands.create('测试日程', { from: '2026-04-12' });
|
|
75
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should handle api errors gracefully', async () => {
|
|
79
|
+
(apiService.createCalendarEvent as jest.Mock).mockRejectedValueOnce(new Error('API Error'));
|
|
80
|
+
|
|
81
|
+
await calendarCommands.create('测试日程', { from: '2026-04-12', to: '2026-04-13' });
|
|
82
|
+
|
|
83
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('list', () => {
|
|
88
|
+
it('should call apiService.listCalendarEvents without filters', async () => {
|
|
89
|
+
await calendarCommands.list();
|
|
90
|
+
expect(apiService.listCalendarEvents).toHaveBeenCalledWith(undefined, undefined);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should call apiService.listCalendarEvents with from filter', async () => {
|
|
94
|
+
await calendarCommands.list({ from: '2026-04-01' });
|
|
95
|
+
expect(apiService.listCalendarEvents).toHaveBeenCalledWith('2026-04-01', undefined);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should call apiService.listCalendarEvents with to filter', async () => {
|
|
99
|
+
await calendarCommands.list({ to: '2026-04-30' });
|
|
100
|
+
expect(apiService.listCalendarEvents).toHaveBeenCalledWith(undefined, '2026-04-30');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should call apiService.listCalendarEvents with both filters', async () => {
|
|
104
|
+
await calendarCommands.list({ from: '2026-04-01', to: '2026-04-30' });
|
|
105
|
+
expect(apiService.listCalendarEvents).toHaveBeenCalledWith('2026-04-01', '2026-04-30');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should handle empty results', async () => {
|
|
109
|
+
(apiService.listCalendarEvents as jest.Mock).mockResolvedValueOnce({ data: [] });
|
|
110
|
+
|
|
111
|
+
await calendarCommands.list();
|
|
112
|
+
|
|
113
|
+
expect(apiService.listCalendarEvents).toHaveBeenCalled();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should handle api errors gracefully', async () => {
|
|
117
|
+
(apiService.listCalendarEvents as jest.Mock).mockRejectedValueOnce(new Error('Network Error'));
|
|
118
|
+
|
|
119
|
+
await calendarCommands.list();
|
|
120
|
+
|
|
121
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Command Tests
|
|
3
|
+
* Comprehensive CLI integration tests for biz health commands
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { healthCommands } from '../../src/commands/biz/health';
|
|
7
|
+
import { setGlobalFlags } from '../../src/utils/output';
|
|
8
|
+
|
|
9
|
+
// Mock apiService
|
|
10
|
+
jest.mock('../../src/services/api', () => ({
|
|
11
|
+
apiService: {
|
|
12
|
+
logHealthData: jest.fn().mockResolvedValue({ id: 1, dataType: 'heart_rate', value: '72' }),
|
|
13
|
+
listHealthData: jest.fn().mockResolvedValue({ data: [{ id: 1, dataType: 'heart_rate', value: '72' }] }),
|
|
14
|
+
syncHealthData: jest.fn().mockResolvedValue({ synced: true }),
|
|
15
|
+
},
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
import { apiService } from '../../src/services/api';
|
|
19
|
+
|
|
20
|
+
// Mock process.exit to prevent test from exiting
|
|
21
|
+
const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {}) as any);
|
|
22
|
+
|
|
23
|
+
// Suppress console output during tests
|
|
24
|
+
beforeAll(() => {
|
|
25
|
+
setGlobalFlags({ quiet: true });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterAll(() => {
|
|
29
|
+
setGlobalFlags({ quiet: false });
|
|
30
|
+
mockExit.mockRestore();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('biz health commands', () => {
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
jest.clearAllMocks();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('log', () => {
|
|
39
|
+
it('should call apiService.logHealthData with required options', async () => {
|
|
40
|
+
await healthCommands.log({ dataType: 'heart_rate', value: '72' });
|
|
41
|
+
expect(apiService.logHealthData).toHaveBeenCalledWith(
|
|
42
|
+
'heart_rate',
|
|
43
|
+
'72',
|
|
44
|
+
undefined,
|
|
45
|
+
undefined
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should call apiService.logHealthData with unit', async () => {
|
|
50
|
+
await healthCommands.log({ dataType: 'heart_rate', value: '72', unit: 'bpm' });
|
|
51
|
+
expect(apiService.logHealthData).toHaveBeenCalledWith(
|
|
52
|
+
'heart_rate',
|
|
53
|
+
'72',
|
|
54
|
+
'bpm',
|
|
55
|
+
undefined
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should call apiService.logHealthData with date', async () => {
|
|
60
|
+
await healthCommands.log({ dataType: 'heart_rate', value: '72', date: '2026-04-12' });
|
|
61
|
+
expect(apiService.logHealthData).toHaveBeenCalledWith(
|
|
62
|
+
'heart_rate',
|
|
63
|
+
'72',
|
|
64
|
+
undefined,
|
|
65
|
+
'2026-04-12'
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should call apiService.logHealthData with all options', async () => {
|
|
70
|
+
await healthCommands.log({
|
|
71
|
+
dataType: 'weight',
|
|
72
|
+
value: '70.5',
|
|
73
|
+
unit: 'kg',
|
|
74
|
+
date: '2026-04-12',
|
|
75
|
+
});
|
|
76
|
+
expect(apiService.logHealthData).toHaveBeenCalledWith(
|
|
77
|
+
'weight',
|
|
78
|
+
'70.5',
|
|
79
|
+
'kg',
|
|
80
|
+
'2026-04-12'
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should exit with error when dataType is missing', async () => {
|
|
85
|
+
await healthCommands.log({ value: '72' });
|
|
86
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should exit with error when value is missing', async () => {
|
|
90
|
+
await healthCommands.log({ dataType: 'heart_rate' });
|
|
91
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should handle api errors gracefully', async () => {
|
|
95
|
+
(apiService.logHealthData as jest.Mock).mockRejectedValueOnce(new Error('API Error'));
|
|
96
|
+
|
|
97
|
+
await healthCommands.log({ dataType: 'heart_rate', value: '72' });
|
|
98
|
+
|
|
99
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('list', () => {
|
|
104
|
+
it('should call apiService.listHealthData without filters', async () => {
|
|
105
|
+
await healthCommands.list();
|
|
106
|
+
expect(apiService.listHealthData).toHaveBeenCalledWith(undefined, undefined, undefined);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should call apiService.listHealthData with dataType filter', async () => {
|
|
110
|
+
await healthCommands.list({ dataType: 'heart_rate' });
|
|
111
|
+
expect(apiService.listHealthData).toHaveBeenCalledWith('heart_rate', undefined, undefined);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should call apiService.listHealthData with from date', async () => {
|
|
115
|
+
await healthCommands.list({ from: '2026-04-01' });
|
|
116
|
+
expect(apiService.listHealthData).toHaveBeenCalledWith(undefined, '2026-04-01', undefined);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should call apiService.listHealthData with to date', async () => {
|
|
120
|
+
await healthCommands.list({ to: '2026-04-12' });
|
|
121
|
+
expect(apiService.listHealthData).toHaveBeenCalledWith(undefined, undefined, '2026-04-12');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should call apiService.listHealthData with all filters', async () => {
|
|
125
|
+
await healthCommands.list({ dataType: 'heart_rate', from: '2026-04-01', to: '2026-04-12' });
|
|
126
|
+
expect(apiService.listHealthData).toHaveBeenCalledWith('heart_rate', '2026-04-01', '2026-04-12');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should handle empty results', async () => {
|
|
130
|
+
(apiService.listHealthData as jest.Mock).mockResolvedValueOnce({ data: [] });
|
|
131
|
+
|
|
132
|
+
await healthCommands.list();
|
|
133
|
+
|
|
134
|
+
expect(apiService.listHealthData).toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should handle api errors gracefully', async () => {
|
|
138
|
+
(apiService.listHealthData as jest.Mock).mockRejectedValueOnce(new Error('Network Error'));
|
|
139
|
+
|
|
140
|
+
await healthCommands.list();
|
|
141
|
+
|
|
142
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('sync', () => {
|
|
147
|
+
it('should call apiService.syncHealthData', async () => {
|
|
148
|
+
await healthCommands.sync();
|
|
149
|
+
expect(apiService.syncHealthData).toHaveBeenCalled();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should handle api errors gracefully', async () => {
|
|
153
|
+
(apiService.syncHealthData as jest.Mock).mockRejectedValueOnce(new Error('API Error'));
|
|
154
|
+
|
|
155
|
+
await healthCommands.sync();
|
|
156
|
+
|
|
157
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|