/**
* Unit tests for MCP Tools
*/
import { OpenClawClient } from '../src/openclaw-client.js';
import {
executeSendMessage,
SendMessageTool,
} from '../src/tools/send-message.js';
import {
executeExecuteCommand,
ExecuteCommandTool,
} from '../src/tools/execute-command.js';
import {
executeCreateCalendarEvent,
CreateCalendarEventTool,
} from '../src/tools/create-calendar-event.js';
import { executeSendEmail, SendEmailTool } from '../src/tools/send-email.js';
import { executeGetTaskStatus, GetTaskStatusTool } from '../src/tools/get-task-status.js';
// Mock the OpenClawClient
jest.mock('../src/openclaw-client');
const MockedOpenClawClient = OpenClawClient as jest.MockedClass<typeof OpenClawClient>;
describe('MCP Tools', () => {
let mockClient: jest.Mocked<OpenClawClient>;
beforeEach(() => {
// Create a mock client instance
mockClient = {
sendMessage: jest.fn(),
executeCommand: jest.fn(),
createCalendarEvent: jest.fn(),
sendEmail: jest.fn(),
getTaskStatus: jest.fn(),
} as unknown as jest.Mocked<OpenClawClient>;
jest.clearAllMocks();
});
describe('send_message tool', () => {
it('should have correct tool definition', () => {
expect(SendMessageTool.name).toBe('send_message');
expect(SendMessageTool.description).toBeDefined();
expect(SendMessageTool.inputSchema).toBeDefined();
});
it('should send message successfully', async () => {
mockClient.sendMessage.mockResolvedValue({
success: true,
messageId: 'msg-123',
});
const result = await executeSendMessage(mockClient, {
platform: 'telegram',
recipient: '@testuser',
message: 'Hello, World!',
});
expect(mockClient.sendMessage).toHaveBeenCalledWith({
platform: 'telegram',
recipient: '@testuser',
message: 'Hello, World!',
});
expect(result.isError).toBe(false);
expect(result.content[0].type).toBe('text');
});
it('should return error when platform is missing', async () => {
const result = await executeSendMessage(mockClient, {
platform: undefined as any,
recipient: '@test',
message: 'Test',
});
expect(result.isError).toBe(true);
expect(result.content[0].type).toBe('text');
expect(JSON.parse(result.content[0].text)).toHaveProperty('error');
});
it('should return error when recipient is missing', async () => {
const result = await executeSendMessage(mockClient, {
platform: 'telegram',
recipient: '',
message: 'Test',
});
expect(result.isError).toBe(true);
});
it('should return error when message is empty', async () => {
const result = await executeSendMessage(mockClient, {
platform: 'telegram',
recipient: '@test',
message: '',
});
expect(result.isError).toBe(true);
});
it('should handle API errors', async () => {
mockClient.sendMessage.mockResolvedValue({
success: false,
error: 'API Error',
});
const result = await executeSendMessage(mockClient, {
platform: 'telegram',
recipient: '@test',
message: 'Test',
});
expect(result.isError).toBe(true);
});
it('should handle unexpected errors', async () => {
mockClient.sendMessage.mockRejectedValue(new Error('Network error'));
const result = await executeSendMessage(mockClient, {
platform: 'telegram',
recipient: '@test',
message: 'Test',
});
expect(result.isError).toBe(true);
});
});
describe('execute_command tool', () => {
it('should have correct tool definition', () => {
expect(ExecuteCommandTool.name).toBe('execute_command');
expect(ExecuteCommandTool.description).toBeDefined();
expect(ExecuteCommandTool.inputSchema).toBeDefined();
});
it('should execute command successfully', async () => {
mockClient.executeCommand.mockResolvedValue({
success: true,
output: 'Hello, World!',
exitCode: 0,
});
const result = await executeExecuteCommand(mockClient, {
command: 'echo "Hello, World!"',
});
expect(mockClient.executeCommand).toHaveBeenCalledWith({
command: 'echo "Hello, World!"',
timeout: 30,
async: false,
});
expect(result.isError).toBe(false);
});
it('should execute async command', async () => {
mockClient.executeCommand.mockResolvedValue({
success: true,
taskId: 'task-abc',
});
const result = await executeExecuteCommand(mockClient, {
command: 'npm install',
async: true,
});
expect(mockClient.executeCommand).toHaveBeenCalledWith({
command: 'npm install',
timeout: 30,
async: true,
});
expect(result.isError).toBe(false);
});
it('should validate timeout range', async () => {
const result = await executeExecuteCommand(mockClient, {
command: 'test',
timeout: 500,
});
expect(result.isError).toBe(true);
expect(JSON.parse(result.content[0].text).error).toContain('between 1 and 300');
});
it('should return error when command is empty', async () => {
const result = await executeExecuteCommand(mockClient, {
command: '',
});
expect(result.isError).toBe(true);
});
});
describe('create_calendar_event tool', () => {
it('should have correct tool definition', () => {
expect(CreateCalendarEventTool.name).toBe('create_calendar_event');
expect(CreateCalendarEventTool.description).toBeDefined();
expect(CreateCalendarEventTool.inputSchema).toBeDefined();
});
it('should create event successfully', async () => {
mockClient.createCalendarEvent.mockResolvedValue({
success: true,
eventId: 'event-123',
});
const result = await executeCreateCalendarEvent(mockClient, {
title: 'Test Event',
startTime: '2024-12-15T10:00:00Z',
});
expect(mockClient.createCalendarEvent).toHaveBeenCalledWith({
title: 'Test Event',
startTime: '2024-12-15T10:00:00Z',
});
expect(result.isError).toBe(false);
});
it('should validate ISO date format', async () => {
const result = await executeCreateCalendarEvent(mockClient, {
title: 'Test',
startTime: 'invalid-date',
});
expect(result.isError).toBe(true);
expect(JSON.parse(result.content[0].text).error).toContain('ISO 8601');
});
it('should validate end time is after start time', async () => {
const result = await executeCreateCalendarEvent(mockClient, {
title: 'Test',
startTime: '2024-12-15T11:00:00Z',
endTime: '2024-12-15T10:00:00Z',
});
expect(result.isError).toBe(true);
expect(JSON.parse(result.content[0].text).error).toContain('after start time');
});
it('should validate email format for attendees', async () => {
const result = await executeCreateCalendarEvent(mockClient, {
title: 'Test',
startTime: '2024-12-15T10:00:00Z',
attendees: ['valid@example.com', 'invalid-email'],
});
expect(result.isError).toBe(true);
expect(JSON.parse(result.content[0].text).error).toContain('Invalid email');
});
it('should return error when title is empty', async () => {
const result = await executeCreateCalendarEvent(mockClient, {
title: '',
startTime: '2024-12-15T10:00:00Z',
});
expect(result.isError).toBe(true);
});
});
describe('send_email tool', () => {
it('should have correct tool definition', () => {
expect(SendEmailTool.name).toBe('send_email');
expect(SendEmailTool.description).toBeDefined();
expect(SendEmailTool.inputSchema).toBeDefined();
});
it('should send email successfully', async () => {
mockClient.sendEmail.mockResolvedValue({
success: true,
messageId: 'email-123',
});
const result = await executeSendEmail(mockClient, {
to: 'test@example.com',
subject: 'Test Subject',
body: 'Test body',
});
expect(mockClient.sendEmail).toHaveBeenCalledWith({
to: ['test@example.com'],
subject: 'Test Subject',
body: 'Test body',
html: false,
});
expect(result.isError).toBe(false);
});
it('should handle multiple recipients', async () => {
mockClient.sendEmail.mockResolvedValue({
success: true,
messageId: 'email-multi',
});
const result = await executeSendEmail(mockClient, {
to: ['user1@example.com', 'user2@example.com'],
subject: 'Test',
body: 'Body',
});
expect(mockClient.sendEmail).toHaveBeenCalledWith({
to: ['user1@example.com', 'user2@example.com'],
subject: 'Test',
body: 'Body',
html: false,
});
expect(result.isError).toBe(false);
});
it('should validate email format', async () => {
const result = await executeSendEmail(mockClient, {
to: 'invalid-email',
subject: 'Test',
body: 'Body',
});
expect(result.isError).toBe(true);
expect(JSON.parse(result.content[0].text).error).toContain('Invalid email');
});
it('should validate CC email format', async () => {
const result = await executeSendEmail(mockClient, {
to: 'valid@example.com',
subject: 'Test',
body: 'Body',
cc: 'invalid-email',
});
expect(result.isError).toBe(true);
expect(JSON.parse(result.content[0].text).error).toContain('cc');
});
it('should return error when subject is empty', async () => {
const result = await executeSendEmail(mockClient, {
to: 'test@example.com',
subject: '',
body: 'Body',
});
expect(result.isError).toBe(true);
});
it('should return error when body is empty', async () => {
const result = await executeSendEmail(mockClient, {
to: 'test@example.com',
subject: 'Test',
body: '',
});
expect(result.isError).toBe(true);
});
});
describe('get_task_status tool', () => {
it('should have correct tool definition', () => {
expect(GetTaskStatusTool.name).toBe('get_task_status');
expect(GetTaskStatusTool.description).toBeDefined();
expect(GetTaskStatusTool.inputSchema).toBeDefined();
});
it('should get task status successfully', async () => {
mockClient.getTaskStatus.mockResolvedValue({
success: true,
taskId: 'task-abc',
status: 'completed',
output: 'Done',
exitCode: 0,
});
const result = await executeGetTaskStatus(mockClient, {
taskId: 'task-abc',
});
expect(mockClient.getTaskStatus).toHaveBeenCalledWith({
taskId: 'task-abc',
});
expect(result.isError).toBe(false);
const parsed = JSON.parse(result.content[0].text);
expect(parsed.status).toBe('completed');
expect(parsed.description).toBeDefined();
});
it('should return status description for pending', async () => {
mockClient.getTaskStatus.mockResolvedValue({
success: true,
taskId: 'task-abc',
status: 'pending',
});
const result = await executeGetTaskStatus(mockClient, {
taskId: 'task-abc',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.status).toBe('pending');
expect(parsed.description).toBe('Task is waiting to start');
});
it('should return status description for running', async () => {
mockClient.getTaskStatus.mockResolvedValue({
success: true,
taskId: 'task-abc',
status: 'running',
});
const result = await executeGetTaskStatus(mockClient, {
taskId: 'task-abc',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.status).toBe('running');
expect(parsed.description).toBe('Task is currently executing');
});
it('should return status description for failed', async () => {
mockClient.getTaskStatus.mockResolvedValue({
success: true,
taskId: 'task-abc',
status: 'failed',
});
const result = await executeGetTaskStatus(mockClient, {
taskId: 'task-abc',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.status).toBe('failed');
expect(parsed.description).toBe('Task failed during execution');
});
it('should return error when taskId is empty', async () => {
const result = await executeGetTaskStatus(mockClient, {
taskId: '',
});
expect(result.isError).toBe(true);
expect(JSON.parse(result.content[0].text).error).toContain('Task ID is required');
});
it('should handle API errors', async () => {
mockClient.getTaskStatus.mockResolvedValue({
success: false,
error: 'Task not found',
});
const result = await executeGetTaskStatus(mockClient, {
taskId: 'nonexistent',
});
expect(result.isError).toBe(true);
});
});
});