tool-registry.test.js 4.7 KB
/**
 * Unit Tests — tools/tool-registry.js
 */
import { describe, it, expect, beforeEach } from 'vitest';

const { ToolRegistry } = require('../../src/tools/tool-registry');

describe('ToolRegistry', () => {
  let registry;

  beforeEach(() => {
    registry = new ToolRegistry();
  });

  describe('register', () => {
    it('should register a tool', () => {
      registry.register({
        name: 'test_tool',
        description: 'A test tool',
        execute: async () => 'ok',
      });
      expect(registry.get('test_tool')).toBeDefined();
    });

    it('should throw for tool without name', () => {
      expect(() => registry.register({ execute: async () => {} })).toThrow();
    });

    it('should throw for tool without execute', () => {
      expect(() => registry.register({ name: 'broken' })).toThrow();
    });
  });

  describe('registerAll', () => {
    it('should register multiple tools', () => {
      registry.registerAll([
        { name: 'tool_a', execute: async () => 'a' },
        { name: 'tool_b', execute: async () => 'b' },
      ]);
      expect(registry.get('tool_a')).toBeDefined();
      expect(registry.get('tool_b')).toBeDefined();
    });
  });

  describe('get', () => {
    it('should return undefined for unknown tools', () => {
      expect(registry.get('nonexistent')).toBeUndefined();
    });
  });

  describe('execute', () => {
    it('should execute a tool and return success', async () => {
      registry.register({
        name: 'greet',
        execute: async (params) => `Hello ${params.name}`,
      });
      const result = await registry.execute('greet', { name: 'World' }, {});
      expect(result.status).toBe('success');
      expect(result.result).toBe('Hello World');
    });

    it('should return error for unknown tool', async () => {
      const result = await registry.execute('no_tool', {}, {});
      expect(result.status).toBe('error');
      expect(result.error).toContain('Unknown tool');
    });

    it('should catch and return tool execution errors', async () => {
      registry.register({
        name: 'failing_tool',
        execute: async () => { throw new Error('Tool broke'); },
      });
      const result = await registry.execute('failing_tool', {}, {});
      expect(result.status).toBe('error');
      expect(result.error).toBe('Tool broke');
    });
  });

  describe('getSchemas', () => {
    it('should return schemas for all registered tools', () => {
      registry.register({
        name: 'tool_x',
        description: 'Does X',
        parameters: { type: 'object', properties: { a: { type: 'string' } } },
        execute: async () => {},
      });
      const schemas = registry.getSchemas();
      expect(schemas).toHaveLength(1);
      expect(schemas[0].name).toBe('tool_x');
      expect(schemas[0].description).toBe('Does X');
    });
  });

  describe('generateToolPrompt', () => {
    it('should return empty string when no tools', () => {
      expect(registry.generateToolPrompt()).toBe('');
    });

    it('should generate tool descriptions', () => {
      registry.register({
        name: 'calculator',
        description: 'Performs math',
        parameters: {
          type: 'object',
          required: ['expression'],
          properties: {
            expression: { type: 'string', description: 'Math expression' },
          },
        },
        execute: async () => {},
      });
      const prompt = registry.generateToolPrompt();
      expect(prompt).toContain('calculator');
      expect(prompt).toContain('Performs math');
      expect(prompt).toContain('expression');
      expect(prompt).toContain('TOOL_CALL');
    });
  });

  describe('parseToolCalls (static)', () => {
    it('should parse tool calls from output', () => {
      const output = `Let me call a tool.
[TOOL_CALL: calculator]
{"expression": "2+2"}
[/TOOL_CALL]
Done.`;
      const calls = ToolRegistry.parseToolCalls(output);
      expect(calls).toHaveLength(1);
      expect(calls[0].tool).toBe('calculator');
      expect(calls[0].params.expression).toBe('2+2');
    });

    it('should parse multiple tool calls', () => {
      const output = `
[TOOL_CALL: tool_a]
{"x": 1}
[/TOOL_CALL]
Some text
[TOOL_CALL: tool_b]
{"y": 2}
[/TOOL_CALL]`;
      const calls = ToolRegistry.parseToolCalls(output);
      expect(calls).toHaveLength(2);
    });

    it('should handle non-JSON params as task text', () => {
      const output = `[TOOL_CALL: run_agent]
Please do this task for me
[/TOOL_CALL]`;
      const calls = ToolRegistry.parseToolCalls(output);
      expect(calls).toHaveLength(1);
      expect(calls[0].params.task).toContain('Please do this');
    });

    it('should return empty array for no tool calls', () => {
      expect(ToolRegistry.parseToolCalls('No tools here')).toEqual([]);
    });
  });
});