@zhin.js/ai 0.0.1

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.
@@ -0,0 +1,800 @@
1
+ /**
2
+ * Tool Service 测试
3
+ *
4
+ * 测试内容:
5
+ * 1. ZhinTool 类的链式调用
6
+ * 2. defineTool 辅助函数
7
+ * 3. 工具参数构建
8
+ * 4. 权限级别判断
9
+ * 5. 命令模式生成
10
+ */
11
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
12
+ import {
13
+ ZhinTool,
14
+ defineTool,
15
+ isZhinTool,
16
+ generatePattern,
17
+ extractParamInfo,
18
+ hasPermissionLevel,
19
+ inferPermissionLevel,
20
+ canAccessTool,
21
+ PERMISSION_LEVEL_PRIORITY,
22
+ } from '@zhin.js/core';
23
+ import type { Tool, ToolContext, ToolPermissionLevel } from '@zhin.js/core';
24
+
25
+ describe('ZhinTool 类', () => {
26
+ it('应该能创建基本工具', () => {
27
+ const tool = new ZhinTool('test_tool')
28
+ .desc('测试工具')
29
+ .execute(async () => '结果');
30
+
31
+ expect(tool.name).toBe('test_tool');
32
+ expect(tool.description).toBe('测试工具');
33
+ });
34
+
35
+ it('应该支持链式调用添加参数', () => {
36
+ const tool = new ZhinTool('weather')
37
+ .desc('查询天气')
38
+ .param('city', { type: 'string', description: '城市名' }, true)
39
+ .param('days', { type: 'number', description: '天数' })
40
+ .execute(async () => '晴天');
41
+
42
+ const params = tool.params;
43
+ expect(params).toHaveLength(2);
44
+ expect(params[0].name).toBe('city');
45
+ expect(params[0].required).toBe(true);
46
+ expect(params[1].name).toBe('days');
47
+ expect(params[1].required).toBe(false);
48
+ });
49
+
50
+ it('应该能设置平台和场景限制', () => {
51
+ const tool = new ZhinTool('group_tool')
52
+ .desc('群聊工具')
53
+ .platform('qq', 'telegram')
54
+ .scope('group')
55
+ .execute(async () => '结果');
56
+
57
+ const toolObj = tool.toTool();
58
+ expect(toolObj.platforms).toEqual(['qq', 'telegram']);
59
+ expect(toolObj.scopes).toEqual(['group']);
60
+ });
61
+
62
+ it('应该能设置权限级别', () => {
63
+ const tool = new ZhinTool('admin_tool')
64
+ .desc('管理员工具')
65
+ .permission('bot_admin')
66
+ .execute(async () => '结果');
67
+
68
+ const toolObj = tool.toTool();
69
+ expect(toolObj.permissionLevel).toBe('bot_admin');
70
+ });
71
+
72
+ it('应该能转换为 Tool 对象', () => {
73
+ const tool = new ZhinTool('calculator')
74
+ .desc('计算器')
75
+ .param('expression', { type: 'string', description: '表达式' }, true)
76
+ .execute(async (args) => `结果: ${args.expression}`);
77
+
78
+ const toolObj = tool.toTool();
79
+
80
+ expect(toolObj.name).toBe('calculator');
81
+ expect(toolObj.description).toBe('计算器');
82
+ expect(toolObj.parameters.properties).toHaveProperty('expression');
83
+ expect(toolObj.parameters.required).toContain('expression');
84
+ expect(typeof toolObj.execute).toBe('function');
85
+ });
86
+
87
+ it('没有 execute 时应该抛出错误', () => {
88
+ const tool = new ZhinTool('no_execute')
89
+ .desc('没有执行函数');
90
+
91
+ expect(() => tool.toTool()).toThrow('has no execute()');
92
+ });
93
+
94
+ it('应该能生成帮助信息', () => {
95
+ const tool = new ZhinTool('help_test')
96
+ .desc('帮助测试')
97
+ .param('name', { type: 'string', description: '名字' }, true)
98
+ .permission('group_admin')
99
+ .platform('qq')
100
+ .scope('group')
101
+ .execute(async () => '');
102
+
103
+ const help = tool.help;
104
+
105
+ expect(help).toContain('help_test');
106
+ expect(help).toContain('帮助测试');
107
+ expect(help).toContain('name');
108
+ expect(help).toContain('group_admin');
109
+ expect(help).toContain('qq');
110
+ expect(help).toContain('group');
111
+ });
112
+
113
+ it('应该能设置命令配置', () => {
114
+ const tool = new ZhinTool('cmd_test')
115
+ .desc('命令测试')
116
+ .usage('使用说明1', '使用说明2')
117
+ .examples('/cmd_test arg1', '/cmd_test arg2')
118
+ .alias('别名1', '别名2')
119
+ .execute(async () => '');
120
+
121
+ const toolObj = tool.toTool();
122
+
123
+ // 没有设置 action,command 应该是 false
124
+ expect(toolObj.command).toBe(false);
125
+ });
126
+
127
+ it('设置 action 后应该生成命令配置', () => {
128
+ const tool = new ZhinTool('with_action')
129
+ .desc('带命令的工具')
130
+ .param('arg', { type: 'string' }, true)
131
+ .usage('使用说明')
132
+ .execute(async () => '')
133
+ .action(async () => '命令结果');
134
+
135
+ const toolObj = tool.toTool();
136
+
137
+ expect(toolObj.command).not.toBe(false);
138
+ expect((toolObj.command as any).enabled).toBe(true);
139
+ expect((toolObj.command as any).usage).toContain('使用说明');
140
+ });
141
+ });
142
+
143
+ describe('defineTool 辅助函数', () => {
144
+ it('应该创建带类型的工具', () => {
145
+ const tool = defineTool<{ city: string; days?: number }>({
146
+ name: 'weather',
147
+ description: '天气查询',
148
+ parameters: {
149
+ type: 'object',
150
+ properties: {
151
+ city: { type: 'string', description: '城市' },
152
+ days: { type: 'number', description: '天数' },
153
+ },
154
+ required: ['city'],
155
+ },
156
+ execute: async (args) => {
157
+ // args.city 应该有类型提示
158
+ return `${args.city} 的天气`;
159
+ },
160
+ });
161
+
162
+ expect(tool.name).toBe('weather');
163
+ expect(tool.parameters.required).toContain('city');
164
+ });
165
+
166
+ it('应该支持命令配置', () => {
167
+ const tool = defineTool({
168
+ name: 'calc',
169
+ description: '计算器',
170
+ parameters: { type: 'object', properties: {} },
171
+ command: {
172
+ pattern: 'calc <expr:rest>',
173
+ alias: ['计算'],
174
+ },
175
+ execute: async () => '结果',
176
+ });
177
+
178
+ expect(tool.command).toBeTruthy();
179
+ expect((tool.command as any).pattern).toBe('calc <expr:rest>');
180
+ });
181
+ });
182
+
183
+ describe('isZhinTool 函数', () => {
184
+ it('应该正确识别 ZhinTool 实例', () => {
185
+ const zhinTool = new ZhinTool('test').execute(async () => '');
186
+ const plainTool: Tool = {
187
+ name: 'plain',
188
+ description: '',
189
+ parameters: { type: 'object', properties: {} },
190
+ execute: async () => '',
191
+ };
192
+
193
+ expect(isZhinTool(zhinTool)).toBe(true);
194
+ expect(isZhinTool(plainTool)).toBe(false);
195
+ expect(isZhinTool(null)).toBe(false);
196
+ expect(isZhinTool(undefined)).toBe(false);
197
+ });
198
+ });
199
+
200
+ describe('generatePattern 函数', () => {
201
+ it('应该生成基本命令模式', () => {
202
+ const tool: Tool = {
203
+ name: 'test',
204
+ description: '',
205
+ parameters: { type: 'object', properties: {} },
206
+ execute: async () => '',
207
+ };
208
+
209
+ expect(generatePattern(tool)).toBe('test');
210
+ });
211
+
212
+ it('应该生成带参数的命令模式', () => {
213
+ const tool: Tool = {
214
+ name: 'weather',
215
+ description: '',
216
+ parameters: {
217
+ type: 'object',
218
+ properties: {
219
+ city: { type: 'string', description: '城市' },
220
+ days: { type: 'number', description: '天数' },
221
+ },
222
+ required: ['city'],
223
+ },
224
+ execute: async () => '',
225
+ };
226
+
227
+ const pattern = generatePattern(tool);
228
+
229
+ expect(pattern).toContain('weather');
230
+ expect(pattern).toContain('<city:text>');
231
+ expect(pattern).toContain('[days:number]');
232
+ });
233
+
234
+ it('应该使用自定义模式', () => {
235
+ const tool: Tool = {
236
+ name: 'custom',
237
+ description: '',
238
+ parameters: { type: 'object', properties: {} },
239
+ command: { pattern: 'custom <arg1> [arg2]' },
240
+ execute: async () => '',
241
+ };
242
+
243
+ expect(generatePattern(tool)).toBe('custom <arg1> [arg2]');
244
+ });
245
+ });
246
+
247
+ describe('extractParamInfo 函数', () => {
248
+ it('应该提取参数信息', () => {
249
+ const params = extractParamInfo({
250
+ type: 'object',
251
+ properties: {
252
+ name: { type: 'string', description: '名字' },
253
+ age: { type: 'number', description: '年龄', default: 18 },
254
+ gender: { type: 'string', enum: ['male', 'female'] },
255
+ },
256
+ required: ['name'],
257
+ });
258
+
259
+ expect(params).toHaveLength(3);
260
+
261
+ const nameParam = params.find(p => p.name === 'name');
262
+ expect(nameParam?.required).toBe(true);
263
+ expect(nameParam?.type).toBe('string');
264
+
265
+ const ageParam = params.find(p => p.name === 'age');
266
+ expect(ageParam?.required).toBe(false);
267
+ expect(ageParam?.default).toBe(18);
268
+
269
+ const genderParam = params.find(p => p.name === 'gender');
270
+ expect(genderParam?.enum).toEqual(['male', 'female']);
271
+ });
272
+ });
273
+
274
+ describe('权限级别判断', () => {
275
+ it('PERMISSION_LEVEL_PRIORITY 应该正确排序', () => {
276
+ expect(PERMISSION_LEVEL_PRIORITY['user']).toBeLessThan(PERMISSION_LEVEL_PRIORITY['group_admin']);
277
+ expect(PERMISSION_LEVEL_PRIORITY['group_admin']).toBeLessThan(PERMISSION_LEVEL_PRIORITY['group_owner']);
278
+ expect(PERMISSION_LEVEL_PRIORITY['group_owner']).toBeLessThan(PERMISSION_LEVEL_PRIORITY['bot_admin']);
279
+ expect(PERMISSION_LEVEL_PRIORITY['bot_admin']).toBeLessThan(PERMISSION_LEVEL_PRIORITY['owner']);
280
+ });
281
+
282
+ it('hasPermissionLevel 应该正确比较权限', () => {
283
+ expect(hasPermissionLevel('owner', 'user')).toBe(true);
284
+ expect(hasPermissionLevel('owner', 'owner')).toBe(true);
285
+ expect(hasPermissionLevel('user', 'owner')).toBe(false);
286
+ expect(hasPermissionLevel('group_admin', 'group_admin')).toBe(true);
287
+ expect(hasPermissionLevel('group_admin', 'group_owner')).toBe(false);
288
+ });
289
+
290
+ it('inferPermissionLevel 应该正确推断权限', () => {
291
+ expect(inferPermissionLevel({ isOwner: true } as ToolContext)).toBe('owner');
292
+ expect(inferPermissionLevel({ isBotAdmin: true } as ToolContext)).toBe('bot_admin');
293
+ expect(inferPermissionLevel({ isGroupOwner: true } as ToolContext)).toBe('group_owner');
294
+ expect(inferPermissionLevel({ isGroupAdmin: true } as ToolContext)).toBe('group_admin');
295
+ expect(inferPermissionLevel({} as ToolContext)).toBe('user');
296
+ expect(inferPermissionLevel({ senderPermissionLevel: 'bot_admin' } as ToolContext)).toBe('bot_admin');
297
+ });
298
+ });
299
+
300
+ describe('canAccessTool 函数', () => {
301
+ const baseTool: Tool = {
302
+ name: 'test',
303
+ description: '',
304
+ parameters: { type: 'object', properties: {} },
305
+ execute: async () => '',
306
+ };
307
+
308
+ it('无限制的工具应该对所有人可用', () => {
309
+ expect(canAccessTool(baseTool, {} as ToolContext)).toBe(true);
310
+ });
311
+
312
+ it('应该正确检查平台限制', () => {
313
+ const tool: Tool = { ...baseTool, platforms: ['qq', 'telegram'] };
314
+
315
+ expect(canAccessTool(tool, { platform: 'qq' } as ToolContext)).toBe(true);
316
+ expect(canAccessTool(tool, { platform: 'telegram' } as ToolContext)).toBe(true);
317
+ expect(canAccessTool(tool, { platform: 'discord' } as ToolContext)).toBe(false);
318
+ expect(canAccessTool(tool, {} as ToolContext)).toBe(false);
319
+ });
320
+
321
+ it('应该正确检查场景限制', () => {
322
+ const tool: Tool = { ...baseTool, scopes: ['group'] };
323
+
324
+ expect(canAccessTool(tool, { scope: 'group' } as ToolContext)).toBe(true);
325
+ expect(canAccessTool(tool, { scope: 'private' } as ToolContext)).toBe(false);
326
+ });
327
+
328
+ it('应该正确检查权限级别', () => {
329
+ const tool: Tool = { ...baseTool, permissionLevel: 'group_admin' };
330
+
331
+ expect(canAccessTool(tool, { isGroupAdmin: true } as ToolContext)).toBe(true);
332
+ expect(canAccessTool(tool, { isGroupOwner: true } as ToolContext)).toBe(true);
333
+ expect(canAccessTool(tool, { isOwner: true } as ToolContext)).toBe(true);
334
+ expect(canAccessTool(tool, {} as ToolContext)).toBe(false);
335
+ });
336
+
337
+ it('应该组合检查所有条件', () => {
338
+ const tool: Tool = {
339
+ ...baseTool,
340
+ platforms: ['qq'],
341
+ scopes: ['group'],
342
+ permissionLevel: 'group_admin',
343
+ };
344
+
345
+ // 全部满足
346
+ expect(canAccessTool(tool, {
347
+ platform: 'qq',
348
+ scope: 'group',
349
+ isGroupAdmin: true,
350
+ } as ToolContext)).toBe(true);
351
+
352
+ // 平台不满足
353
+ expect(canAccessTool(tool, {
354
+ platform: 'telegram',
355
+ scope: 'group',
356
+ isGroupAdmin: true,
357
+ } as ToolContext)).toBe(false);
358
+
359
+ // 场景不满足
360
+ expect(canAccessTool(tool, {
361
+ platform: 'qq',
362
+ scope: 'private',
363
+ isGroupAdmin: true,
364
+ } as ToolContext)).toBe(false);
365
+
366
+ // 权限不满足
367
+ expect(canAccessTool(tool, {
368
+ platform: 'qq',
369
+ scope: 'group',
370
+ } as ToolContext)).toBe(false);
371
+ });
372
+
373
+ it('空平台数组应该允许所有平台', () => {
374
+ const tool: Tool = { ...baseTool, platforms: [] };
375
+
376
+ expect(canAccessTool(tool, { platform: 'qq' } as ToolContext)).toBe(true);
377
+ expect(canAccessTool(tool, { platform: 'telegram' } as ToolContext)).toBe(true);
378
+ });
379
+
380
+ it('空场景数组应该允许所有场景', () => {
381
+ const tool: Tool = { ...baseTool, scopes: [] };
382
+
383
+ expect(canAccessTool(tool, { scope: 'group' } as ToolContext)).toBe(true);
384
+ expect(canAccessTool(tool, { scope: 'private' } as ToolContext)).toBe(true);
385
+ });
386
+ });
387
+
388
+ describe('ZhinTool 高级功能', () => {
389
+ it('应该支持隐藏工具', () => {
390
+ const tool = new ZhinTool('hidden_tool')
391
+ .desc('隐藏工具')
392
+ .hidden()
393
+ .execute(async () => '');
394
+
395
+ const toolObj = tool.toTool();
396
+ expect(toolObj.hidden).toBe(true);
397
+ });
398
+
399
+ it('应该支持设置 hidden 为 false', () => {
400
+ const tool = new ZhinTool('visible_tool')
401
+ .desc('可见工具')
402
+ .hidden(false)
403
+ .execute(async () => '');
404
+
405
+ const toolObj = tool.toTool();
406
+ // hidden(false) 不会设置属性(默认就是不隐藏)
407
+ expect(toolObj.hidden).toBeUndefined();
408
+ });
409
+
410
+ it('应该支持设置标签', () => {
411
+ const tool = new ZhinTool('tagged_tool')
412
+ .desc('带标签工具')
413
+ .tag('utility', 'helper')
414
+ .execute(async () => '');
415
+
416
+ const toolObj = tool.toTool();
417
+ expect(toolObj.tags).toContain('utility');
418
+ expect(toolObj.tags).toContain('helper');
419
+ });
420
+
421
+ it('应该支持设置旧版权限', () => {
422
+ const tool = new ZhinTool('permit_tool')
423
+ .desc('权限工具')
424
+ .permit('admin', 'operator')
425
+ .execute(async () => '');
426
+
427
+ const toolObj = tool.toTool();
428
+ expect(toolObj.permissions).toContain('admin');
429
+ expect(toolObj.permissions).toContain('operator');
430
+ });
431
+
432
+ it('应该支持自定义命令模式', () => {
433
+ const tool = new ZhinTool('custom_pattern')
434
+ .desc('自定义模式')
435
+ .param('arg', { type: 'string' }, true)
436
+ .pattern('custom <arg:rest>')
437
+ .execute(async () => '')
438
+ .action(async () => '');
439
+
440
+ const toolObj = tool.toTool();
441
+ expect((toolObj.command as any).pattern).toBe('custom <arg:rest>');
442
+ });
443
+
444
+ it('应该支持多次调用 param 更新参数', () => {
445
+ const tool = new ZhinTool('update_param')
446
+ .desc('更新参数')
447
+ .param('name', { type: 'string', description: '原始描述' }, false)
448
+ .param('name', { type: 'string', description: '新描述' }, true)
449
+ .execute(async () => '');
450
+
451
+ const params = tool.params;
452
+ expect(params).toHaveLength(1);
453
+ expect(params[0].schema.description).toBe('新描述');
454
+ expect(params[0].required).toBe(true);
455
+ });
456
+
457
+ it('toJSON 应该返回正确的格式', () => {
458
+ const tool = new ZhinTool('json_test')
459
+ .desc('JSON 测试')
460
+ .param('city', { type: 'string', description: '城市' }, true)
461
+ .platform('qq')
462
+ .scope('group')
463
+ .permission('group_admin')
464
+ .tag('test')
465
+ .execute(async () => '');
466
+
467
+ const json = tool.toJSON();
468
+
469
+ expect(json.name).toBe('json_test');
470
+ expect(json.description).toBe('JSON 测试');
471
+ expect(json.parameters.properties).toHaveProperty('city');
472
+ expect(json.platforms).toContain('qq');
473
+ expect(json.scopes).toContain('group');
474
+ expect(json.permissionLevel).toBe('group_admin');
475
+ expect(json.tags).toContain('test');
476
+ // execute 不应该在 JSON 中
477
+ expect(json).not.toHaveProperty('execute');
478
+ });
479
+
480
+ it('toString 应该返回工具描述', () => {
481
+ const tool = new ZhinTool('string_test')
482
+ .desc('字符串测试')
483
+ .execute(async () => '');
484
+
485
+ const str = tool.toString();
486
+
487
+ expect(str).toContain('string_test');
488
+ expect(str).toContain('字符串测试');
489
+ });
490
+
491
+ it('多个场景应该正确设置', () => {
492
+ const tool = new ZhinTool('multi_scope')
493
+ .desc('多场景')
494
+ .scope('group', 'private', 'channel')
495
+ .execute(async () => '');
496
+
497
+ const toolObj = tool.toTool();
498
+ expect(toolObj.scopes).toEqual(['group', 'private', 'channel']);
499
+ });
500
+
501
+ it('多个平台应该正确设置', () => {
502
+ const tool = new ZhinTool('multi_platform')
503
+ .desc('多平台')
504
+ .platform('qq', 'telegram', 'discord')
505
+ .execute(async () => '');
506
+
507
+ const toolObj = tool.toTool();
508
+ expect(toolObj.platforms).toEqual(['qq', 'telegram', 'discord']);
509
+ });
510
+
511
+ it('默认权限级别应该是 user', () => {
512
+ const tool = new ZhinTool('default_perm')
513
+ .desc('默认权限')
514
+ .execute(async () => '');
515
+
516
+ const toolObj = tool.toTool();
517
+ // 默认 user 不会设置到 toolObj
518
+ expect(toolObj.permissionLevel).toBeUndefined();
519
+ });
520
+ });
521
+
522
+ describe('generatePattern 边界情况', () => {
523
+ it('应该处理没有 properties 的参数', () => {
524
+ const tool: Tool = {
525
+ name: 'no_props',
526
+ description: '',
527
+ parameters: { type: 'object' },
528
+ execute: async () => '',
529
+ };
530
+
531
+ expect(generatePattern(tool)).toBe('no_props');
532
+ });
533
+
534
+ it('应该处理空 properties', () => {
535
+ const tool: Tool = {
536
+ name: 'empty_props',
537
+ description: '',
538
+ parameters: { type: 'object', properties: {} },
539
+ execute: async () => '',
540
+ };
541
+
542
+ expect(generatePattern(tool)).toBe('empty_props');
543
+ });
544
+
545
+ it('应该正确处理 number 类型参数', () => {
546
+ const tool: Tool = {
547
+ name: 'number_param',
548
+ description: '',
549
+ parameters: {
550
+ type: 'object',
551
+ properties: {
552
+ count: { type: 'number', description: '数量' },
553
+ },
554
+ required: ['count'],
555
+ },
556
+ execute: async () => '',
557
+ };
558
+
559
+ const pattern = generatePattern(tool);
560
+ expect(pattern).toContain('<count:number>');
561
+ });
562
+
563
+ it('应该正确处理自定义 paramType', () => {
564
+ const tool: Tool = {
565
+ name: 'custom_type',
566
+ description: '',
567
+ parameters: {
568
+ type: 'object',
569
+ properties: {
570
+ content: { type: 'string', paramType: 'rest' as any },
571
+ },
572
+ required: ['content'],
573
+ },
574
+ execute: async () => '',
575
+ };
576
+
577
+ const pattern = generatePattern(tool);
578
+ expect(pattern).toContain('<content:rest>');
579
+ });
580
+ });
581
+
582
+ describe('extractParamInfo 边界情况', () => {
583
+ it('应该处理空 properties', () => {
584
+ const params = extractParamInfo({ type: 'object' });
585
+ expect(params).toEqual([]);
586
+ });
587
+
588
+ it('应该处理没有 required 的参数', () => {
589
+ const params = extractParamInfo({
590
+ type: 'object',
591
+ properties: {
592
+ name: { type: 'string' },
593
+ },
594
+ });
595
+
596
+ expect(params).toHaveLength(1);
597
+ expect(params[0].required).toBe(false);
598
+ });
599
+
600
+ it('应该处理空 required 数组', () => {
601
+ const params = extractParamInfo({
602
+ type: 'object',
603
+ properties: {
604
+ name: { type: 'string' },
605
+ },
606
+ required: [],
607
+ });
608
+
609
+ expect(params[0].required).toBe(false);
610
+ });
611
+ });
612
+
613
+ describe('ZhinTool 参数顺序', () => {
614
+ it('必填参数应该排在可选参数前面', () => {
615
+ const tool = new ZhinTool('ordered')
616
+ .desc('参数顺序测试')
617
+ .param('optional1', { type: 'string' }, false)
618
+ .param('required1', { type: 'string' }, true)
619
+ .param('optional2', { type: 'string' }, false)
620
+ .param('required2', { type: 'string' }, true)
621
+ .execute(async () => '');
622
+
623
+ const toolObj = tool.toTool();
624
+ const required = toolObj.parameters.required || [];
625
+
626
+ expect(required).toContain('required1');
627
+ expect(required).toContain('required2');
628
+ expect(required).not.toContain('optional1');
629
+ expect(required).not.toContain('optional2');
630
+ });
631
+
632
+ it('参数定义顺序应该保持', () => {
633
+ const tool = new ZhinTool('keep_order')
634
+ .desc('保持顺序')
635
+ .param('a', { type: 'string' }, true)
636
+ .param('b', { type: 'string' }, true)
637
+ .param('c', { type: 'string' }, true)
638
+ .execute(async () => '');
639
+
640
+ const params = tool.params;
641
+ expect(params.map(p => p.name)).toEqual(['a', 'b', 'c']);
642
+ });
643
+ });
644
+
645
+ describe('ZhinTool execute 执行', () => {
646
+ it('应该正确执行 execute 函数', async () => {
647
+ const tool = new ZhinTool('exec_test')
648
+ .desc('执行测试')
649
+ .param('name', { type: 'string' }, true)
650
+ .execute(async (args) => `Hello, ${args.name}!`);
651
+
652
+ const toolObj = tool.toTool();
653
+ const result = await toolObj.execute({ name: 'World' });
654
+
655
+ expect(result).toBe('Hello, World!');
656
+ });
657
+
658
+ it('execute 应该接收 context 参数', async () => {
659
+ const mockContext: ToolContext = {
660
+ platform: 'test',
661
+ senderId: 'user1',
662
+ };
663
+
664
+ const tool = new ZhinTool('ctx_test')
665
+ .desc('上下文测试')
666
+ .execute(async (args, ctx) => ctx?.platform || 'no-platform');
667
+
668
+ const toolObj = tool.toTool();
669
+ const result = await toolObj.execute({}, mockContext);
670
+
671
+ expect(result).toBe('test');
672
+ });
673
+ });
674
+
675
+ describe('defineTool 高级用法', () => {
676
+ it('应该支持禁用命令', () => {
677
+ const tool = defineTool({
678
+ name: 'no_cmd',
679
+ description: '无命令',
680
+ parameters: { type: 'object', properties: {} },
681
+ command: false,
682
+ execute: async () => '',
683
+ });
684
+
685
+ expect(tool.command).toBe(false);
686
+ });
687
+
688
+ it('应该支持设置权限', () => {
689
+ const tool = defineTool({
690
+ name: 'with_perm',
691
+ description: '带权限',
692
+ parameters: { type: 'object', properties: {} },
693
+ permissions: ['admin'],
694
+ permissionLevel: 'bot_admin',
695
+ execute: async () => '',
696
+ });
697
+
698
+ expect(tool.permissions).toContain('admin');
699
+ expect(tool.permissionLevel).toBe('bot_admin');
700
+ });
701
+
702
+ it('应该支持设置标签', () => {
703
+ const tool = defineTool({
704
+ name: 'with_tags',
705
+ description: '带标签',
706
+ parameters: { type: 'object', properties: {} },
707
+ tags: ['utility', 'helper'],
708
+ execute: async () => '',
709
+ });
710
+
711
+ expect(tool.tags).toContain('utility');
712
+ expect(tool.tags).toContain('helper');
713
+ });
714
+
715
+ it('应该支持设置平台和场景', () => {
716
+ const tool = defineTool({
717
+ name: 'platform_scope',
718
+ description: '平台场景',
719
+ parameters: { type: 'object', properties: {} },
720
+ platforms: ['qq'],
721
+ scopes: ['group'],
722
+ execute: async () => '',
723
+ });
724
+
725
+ expect(tool.platforms).toContain('qq');
726
+ expect(tool.scopes).toContain('group');
727
+ });
728
+
729
+ it('应该支持 hidden 属性', () => {
730
+ const tool = defineTool({
731
+ name: 'hidden_tool',
732
+ description: '隐藏工具',
733
+ parameters: { type: 'object', properties: {} },
734
+ hidden: true,
735
+ execute: async () => '',
736
+ });
737
+
738
+ expect(tool.hidden).toBe(true);
739
+ });
740
+
741
+ it('应该支持 source 属性', () => {
742
+ const tool = defineTool({
743
+ name: 'sourced',
744
+ description: '带来源',
745
+ parameters: { type: 'object', properties: {} },
746
+ source: 'plugin:my-plugin',
747
+ execute: async () => '',
748
+ });
749
+
750
+ expect(tool.source).toBe('plugin:my-plugin');
751
+ });
752
+ });
753
+
754
+ describe('权限系统完整测试', () => {
755
+ it('owner 权限应该高于所有其他权限', () => {
756
+ expect(hasPermissionLevel('owner', 'user')).toBe(true);
757
+ expect(hasPermissionLevel('owner', 'group_admin')).toBe(true);
758
+ expect(hasPermissionLevel('owner', 'group_owner')).toBe(true);
759
+ expect(hasPermissionLevel('owner', 'bot_admin')).toBe(true);
760
+ expect(hasPermissionLevel('owner', 'owner')).toBe(true);
761
+ });
762
+
763
+ it('user 权限应该只能访问 user 级别工具', () => {
764
+ expect(hasPermissionLevel('user', 'user')).toBe(true);
765
+ expect(hasPermissionLevel('user', 'group_admin')).toBe(false);
766
+ expect(hasPermissionLevel('user', 'group_owner')).toBe(false);
767
+ expect(hasPermissionLevel('user', 'bot_admin')).toBe(false);
768
+ expect(hasPermissionLevel('user', 'owner')).toBe(false);
769
+ });
770
+
771
+ it('group_admin 可以访问 user 和 group_admin 级别', () => {
772
+ expect(hasPermissionLevel('group_admin', 'user')).toBe(true);
773
+ expect(hasPermissionLevel('group_admin', 'group_admin')).toBe(true);
774
+ expect(hasPermissionLevel('group_admin', 'group_owner')).toBe(false);
775
+ });
776
+
777
+ it('bot_admin 可以访问除 owner 外的所有级别', () => {
778
+ expect(hasPermissionLevel('bot_admin', 'user')).toBe(true);
779
+ expect(hasPermissionLevel('bot_admin', 'group_admin')).toBe(true);
780
+ expect(hasPermissionLevel('bot_admin', 'group_owner')).toBe(true);
781
+ expect(hasPermissionLevel('bot_admin', 'bot_admin')).toBe(true);
782
+ expect(hasPermissionLevel('bot_admin', 'owner')).toBe(false);
783
+ });
784
+
785
+ it('inferPermissionLevel 应该优先使用 senderPermissionLevel', () => {
786
+ expect(inferPermissionLevel({
787
+ senderPermissionLevel: 'owner',
788
+ isGroupAdmin: true,
789
+ } as ToolContext)).toBe('owner');
790
+ });
791
+
792
+ it('inferPermissionLevel 在无任何权限时返回 user', () => {
793
+ expect(inferPermissionLevel({
794
+ isGroupAdmin: false,
795
+ isGroupOwner: false,
796
+ isBotAdmin: false,
797
+ isOwner: false,
798
+ } as ToolContext)).toBe('user');
799
+ });
800
+ });