85 lines
2.7 KiB
TypeScript
85 lines
2.7 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { z } from 'zod';
|
|
import { registerTool, getTool, listTools } from './registry.js';
|
|
|
|
describe('tool registry', () => {
|
|
it('registers and retrieves a tool by name', () => {
|
|
registerTool({
|
|
name: 'test.hello',
|
|
description: 'A test tool',
|
|
requiredRole: 'viewer',
|
|
inputSchema: z.object({ name: z.string() }),
|
|
async execute(args) {
|
|
return { hello: args.name };
|
|
},
|
|
});
|
|
const tool = getTool('test.hello');
|
|
expect(tool).toBeDefined();
|
|
expect(tool?.name).toBe('test.hello');
|
|
expect(tool?.requiredRole).toBe('viewer');
|
|
});
|
|
|
|
it('listTools returns all registered tools with meta', () => {
|
|
const all = listTools();
|
|
const found = all.find(t => t.name === 'test.hello');
|
|
expect(found).toBeDefined();
|
|
expect(found?.description).toBe('A test tool');
|
|
expect(found?.inputSchema).toHaveProperty('type', 'object');
|
|
});
|
|
|
|
it('getTool returns undefined for unknown tool', () => {
|
|
expect(getTool('does.not.exist')).toBeUndefined();
|
|
});
|
|
|
|
it('zodToJsonSchema emits correct types for boolean/number/array fields', () => {
|
|
registerTool({
|
|
name: 'test.types',
|
|
description: 'Type test',
|
|
requiredRole: 'viewer',
|
|
inputSchema: z.object({
|
|
flag: z.boolean(),
|
|
count: z.coerce.number(),
|
|
tags: z.array(z.string()),
|
|
label: z.string().optional(),
|
|
enabled: z.boolean().default(false),
|
|
}),
|
|
async execute(args) {
|
|
return args;
|
|
},
|
|
});
|
|
const tool = listTools().find(t => t.name === 'test.types');
|
|
const props = (tool?.inputSchema as { properties: Record<string, { type: string }> })
|
|
.properties;
|
|
expect(props.flag.type).toBe('boolean');
|
|
expect(props.count.type).toBe('number');
|
|
expect(props.tags.type).toBe('array');
|
|
expect(props.label.type).toBe('string');
|
|
// ZodDefault should unwrap to boolean, not 'string'
|
|
expect(props.enabled.type).toBe('boolean');
|
|
// optional + default fields should NOT be in required
|
|
const required = tool?.inputSchema.required as string[];
|
|
expect(required).toContain('flag');
|
|
expect(required).toContain('count');
|
|
expect(required).not.toContain('label');
|
|
expect(required).not.toContain('enabled');
|
|
});
|
|
});
|
|
|
|
describe('tool execute', () => {
|
|
it('executes with validated args', async () => {
|
|
registerTool({
|
|
name: 'test.echo',
|
|
description: 'Echo',
|
|
requiredRole: 'admin',
|
|
inputSchema: z.object({ value: z.string() }),
|
|
async execute(args) {
|
|
return { echoed: args.value };
|
|
},
|
|
});
|
|
|
|
const tool = getTool('test.echo')!;
|
|
const result = await tool.execute({ value: 'ping' }, {} as never);
|
|
expect(result).toEqual({ echoed: 'ping' });
|
|
});
|
|
});
|