| |
| |
| |
| |
| |
|
|
| import { describe, it, expect, vi, beforeEach } from 'vitest'; |
| import { CompleteTaskTool } from './complete-task.js'; |
| import { type MessageBus } from '../confirmation-bus/message-bus.js'; |
| import { z } from 'zod'; |
|
|
| describe('CompleteTaskTool', () => { |
| let mockMessageBus: MessageBus; |
|
|
| beforeEach(() => { |
| mockMessageBus = { |
| publish: vi.fn().mockResolvedValue(undefined), |
| subscribe: vi.fn(), |
| unsubscribe: vi.fn(), |
| } as unknown as MessageBus; |
| }); |
|
|
| describe('Default Configuration (no outputConfig)', () => { |
| let tool: CompleteTaskTool; |
|
|
| beforeEach(() => { |
| tool = new CompleteTaskTool(mockMessageBus); |
| }); |
|
|
| it('should have correct metadata', () => { |
| expect(tool.name).toBe('complete_task'); |
| expect(tool.displayName).toBe('Complete Task'); |
| }); |
|
|
| it('should generate correct schema', () => { |
| const schema = tool.getSchema(); |
| const parameters = schema.parametersJsonSchema as Record<string, unknown>; |
| const properties = parameters['properties'] as Record<string, unknown>; |
|
|
| expect(properties).toHaveProperty('result'); |
| expect(parameters['required']).toContain('result'); |
|
|
| const resultProp = properties['result'] as Record<string, unknown>; |
| expect(resultProp['type']).toBe('string'); |
| }); |
|
|
| it('should validate successfully with result', () => { |
| const result = tool.validateToolParams({ result: 'Task done' }); |
| expect(result).toBeNull(); |
| }); |
|
|
| it('should fail validation if result is missing', () => { |
| const result = tool.validateToolParams({}); |
| expect(result).toContain("must have required property 'result'"); |
| }); |
|
|
| it('should fail validation if result is only whitespace', () => { |
| const result = tool.validateToolParams({ result: ' ' }); |
| expect(result).toContain( |
| 'Missing required "result" argument. You must provide your findings when calling complete_task.', |
| ); |
| }); |
|
|
| it('should execute and return correct data', async () => { |
| const invocation = tool.build({ result: 'Success message' }); |
| const result = await invocation.execute({ |
| abortSignal: new AbortController().signal, |
| }); |
|
|
| expect(result.data).toEqual({ |
| taskCompleted: true, |
| submittedOutput: 'Success message', |
| }); |
| expect(result.returnDisplay).toBe('Result submitted and task completed.'); |
| }); |
| }); |
|
|
| describe('Structured Configuration (with outputConfig)', () => { |
| const schema = z.object({ |
| report: z.string(), |
| score: z.number(), |
| }); |
| const outputConfig = { |
| outputName: 'my_output', |
| description: 'The final report', |
| schema, |
| }; |
| let tool: CompleteTaskTool<typeof schema>; |
|
|
| beforeEach(() => { |
| tool = new CompleteTaskTool(mockMessageBus, outputConfig); |
| }); |
|
|
| it('should generate schema based on outputConfig', () => { |
| const toolSchema = tool.getSchema(); |
|
|
| expect(toolSchema.parametersJsonSchema).toHaveProperty( |
| 'properties.my_output', |
| ); |
| expect(toolSchema.parametersJsonSchema).toHaveProperty( |
| 'properties.my_output.type', |
| 'object', |
| ); |
| expect(toolSchema.parametersJsonSchema).toHaveProperty( |
| 'properties.my_output.properties.report', |
| ); |
| expect(toolSchema.parametersJsonSchema).toHaveProperty( |
| 'properties.my_output.properties.score', |
| ); |
| expect(toolSchema.parametersJsonSchema).toHaveProperty( |
| 'required', |
| expect.arrayContaining(['my_output']), |
| ); |
| }); |
|
|
| it('should validate successfully with correct structure', () => { |
| const result = tool.validateToolParams({ |
| my_output: { report: 'All good', score: 100 }, |
| }); |
| expect(result).toBeNull(); |
| }); |
|
|
| it('should fail validation if output is missing', () => { |
| const result = tool.validateToolParams({}); |
| expect(result).toContain("must have required property 'my_output'"); |
| }); |
|
|
| it('should fail validation if schema mismatch', () => { |
| const result = tool.validateToolParams({ |
| my_output: { report: 'All good', score: 'not a number' }, |
| }); |
| expect(result).toContain('must be number'); |
| }); |
|
|
| it('should execute and return structured data', async () => { |
| const outputValue = { report: 'Final findings', score: 42 }; |
| const invocation = tool.build({ my_output: outputValue }); |
| const result = await invocation.execute({ |
| abortSignal: new AbortController().signal, |
| }); |
|
|
| expect(result.data?.['taskCompleted']).toBe(true); |
| expect(result.data?.['submittedOutput']).toBe( |
| JSON.stringify(outputValue, null, 2), |
| ); |
| }); |
|
|
| it('should use processOutput if provided', async () => { |
| const processOutput = (val: z.infer<typeof schema>) => |
| `Score was ${val.score}`; |
| const toolWithProcess = new CompleteTaskTool( |
| mockMessageBus, |
| outputConfig, |
| processOutput, |
| ); |
|
|
| const outputValue = { report: 'Final findings', score: 42 }; |
| const invocation = toolWithProcess.build({ my_output: outputValue }); |
| const result = await invocation.execute({ |
| abortSignal: new AbortController().signal, |
| }); |
|
|
| expect(result.data?.['submittedOutput']).toBe('Score was 42'); |
| }); |
| }); |
| }); |
|
|