mirror of
https://github.com/hanzoai/mcp.git
synced 2026-10-15 12:30:12 +00:00
Every suite had stopped executing. ts-jest peers on `typescript >=4.3 <7` and this repo builds against 7, so all twenty files died inside ConfigSet with "Cannot read properties of undefined (reading 'fileExists')" — twenty failures, zero tests, and nothing to read them by because the push gate deliberately runs typecheck and build rather than jest. swc transforms instead: it strips types rather than asking the compiler to, so it has no TypeScript version to disagree with, and `tsc --noEmit` already answers the question ts-jest was answering twice. Running as real ESM then exposed what the CommonJS fallback had hidden. jest.mock does not hoist there, so a static import binds the real module and the suite fails on `mockReturnValue is not a function` — an absent mock reported as a broken one. The search, autogui and ui suites now register through unstable_mockModule and import after it. One of them had never mocked fs at all: it cast fs.promises to a mocked type and called mockImplementation on the real module. 384 tests, twenty suites, all passing.
330 lines
12 KiB
TypeScript
330 lines
12 KiB
TypeScript
import { describe, test, expect, beforeEach, afterEach, jest } from '@jest/globals';
|
|
|
|
// Mock external dependencies
|
|
// unstable_mockModule + await import, NOT jest.mock + a static import: ESM has no
|
|
// hoisting for jest.mock to rely on, so a static import binds the REAL module and
|
|
// the suite dies on `mockReturnValue is not a function` — an absent mock reported
|
|
// as a broken one.
|
|
jest.unstable_mockModule('../../src/autogui/factory.js', () => ({
|
|
createAutoGUI: jest.fn(),
|
|
getAvailableAutoGUIImplementations: jest.fn(),
|
|
getAutoGUIImplementationStatus: jest.fn(),
|
|
}));
|
|
|
|
const factory = await import('../../src/autogui/factory.js');
|
|
const { autoguiTools } = await import('../../src/autogui/tools/autogui-tools.js');
|
|
|
|
describe('AutoGUI Tools', () => {
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
|
|
// Mock factory functions
|
|
const { createAutoGUI, getAvailableAutoGUIImplementations, getAutoGUIImplementationStatus } = factory;
|
|
|
|
getAvailableAutoGUIImplementations.mockResolvedValue(['base', 'mock']);
|
|
getAutoGUIImplementationStatus.mockResolvedValue({
|
|
base: { available: true, status: 'ready' },
|
|
mock: { available: true, status: 'ready' }
|
|
});
|
|
|
|
createAutoGUI.mockResolvedValue({
|
|
getImplementation: () => 'mock',
|
|
isAvailable: () => true,
|
|
getScreenSize: jest.fn<() => Promise<any>>().mockResolvedValue({ width: 1920, height: 1080 }),
|
|
screenshot: jest.fn<() => Promise<any>>().mockResolvedValue('base64-image-data'),
|
|
click: jest.fn<() => Promise<any>>().mockResolvedValue(undefined),
|
|
doubleClick: jest.fn<() => Promise<any>>().mockResolvedValue(undefined),
|
|
rightClick: jest.fn<() => Promise<any>>().mockResolvedValue(undefined),
|
|
type: jest.fn<() => Promise<any>>().mockResolvedValue(undefined),
|
|
key: jest.fn<() => Promise<any>>().mockResolvedValue(undefined),
|
|
scroll: jest.fn<() => Promise<any>>().mockResolvedValue(undefined),
|
|
moveMouse: jest.fn<() => Promise<any>>().mockResolvedValue(undefined),
|
|
findElement: jest.fn<() => Promise<any>>().mockResolvedValue({ x: 100, y: 100, width: 50, height: 30 }),
|
|
});
|
|
});
|
|
|
|
describe('autoguiTools array', () => {
|
|
test('should export AutoGUI tools array', () => {
|
|
expect(Array.isArray(autoguiTools)).toBe(true);
|
|
expect(autoguiTools.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('should have valid tool structures', () => {
|
|
autoguiTools.forEach(tool => {
|
|
expect(tool).toHaveProperty('name');
|
|
expect(tool).toHaveProperty('description');
|
|
expect(tool).toHaveProperty('inputSchema');
|
|
expect(tool).toHaveProperty('handler');
|
|
|
|
expect(typeof tool.name).toBe('string');
|
|
expect(typeof tool.description).toBe('string');
|
|
expect(typeof tool.handler).toBe('function');
|
|
expect(tool.inputSchema).toHaveProperty('type');
|
|
expect(tool.inputSchema.type).toBe('object');
|
|
});
|
|
});
|
|
|
|
test('should have unique tool names', () => {
|
|
const names = autoguiTools.map(tool => tool.name);
|
|
const uniqueNames = new Set(names);
|
|
expect(uniqueNames.size).toBe(autoguiTools.length);
|
|
});
|
|
|
|
test('should include core AutoGUI functionality', () => {
|
|
const toolNames = autoguiTools.map(tool => tool.name);
|
|
|
|
// Should include status tool
|
|
expect(toolNames.some(name => name.includes('status'))).toBe(true);
|
|
|
|
// Should include basic interaction tools if available
|
|
const expectedTools = ['status', 'screenshot', 'click', 'type'];
|
|
expectedTools.forEach(expectedTool => {
|
|
expect(toolNames.some(name => name.includes(expectedTool))).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('autogui_status tool', () => {
|
|
test('should have correct metadata', () => {
|
|
const statusTool = autoguiTools.find(tool => tool.name === 'autogui_status');
|
|
expect(statusTool).toBeDefined();
|
|
expect(statusTool!.description).toContain('status');
|
|
expect(statusTool!.inputSchema.required).toEqual([]);
|
|
});
|
|
|
|
test('should return implementation status', async () => {
|
|
const statusTool = autoguiTools.find(tool => tool.name === 'autogui_status');
|
|
|
|
const result = await statusTool!.handler({});
|
|
|
|
expect(result.isError).toBeFalsy();
|
|
expect(result.content).toHaveLength(1);
|
|
expect(result.content[0].type).toBe('text');
|
|
expect(result.content[0].text).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
describe('Screenshot functionality', () => {
|
|
test('should have screenshot tool', () => {
|
|
const screenshotTool = autoguiTools.find(tool =>
|
|
tool.name.includes('screenshot') || tool.description.toLowerCase().includes('screenshot')
|
|
);
|
|
expect(screenshotTool).toBeDefined();
|
|
});
|
|
|
|
test('should handle screenshot requests', async () => {
|
|
const screenshotTool = autoguiTools.find(tool =>
|
|
tool.name.includes('screenshot') || tool.description.toLowerCase().includes('screenshot')
|
|
);
|
|
|
|
if (screenshotTool) {
|
|
try {
|
|
const result = await screenshotTool.handler({});
|
|
expect(result).toHaveProperty('content');
|
|
expect(Array.isArray(result.content)).toBe(true);
|
|
} catch (error) {
|
|
// Acceptable if AutoGUI implementation is not available
|
|
expect(error).toBeDefined();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Mouse interaction tools', () => {
|
|
test('should have click tool', () => {
|
|
const clickTool = autoguiTools.find(tool =>
|
|
tool.name.includes('click') || tool.description.toLowerCase().includes('click')
|
|
);
|
|
expect(clickTool).toBeDefined();
|
|
});
|
|
|
|
test('should validate click coordinates', () => {
|
|
const clickTool = autoguiTools.find(tool =>
|
|
tool.name.includes('click') || tool.description.toLowerCase().includes('click')
|
|
);
|
|
|
|
if (clickTool) {
|
|
const schema = clickTool.inputSchema;
|
|
expect(schema.properties).toHaveProperty('x');
|
|
expect(schema.properties).toHaveProperty('y');
|
|
// x and y are optional for the click tool (clicks at current position
|
|
// when coordinates are omitted), so required is an empty array
|
|
expect(schema.required).toEqual([]);
|
|
}
|
|
});
|
|
|
|
test('should handle click actions', async () => {
|
|
const clickTool = autoguiTools.find(tool =>
|
|
tool.name.includes('click') || tool.description.toLowerCase().includes('click')
|
|
);
|
|
|
|
if (clickTool) {
|
|
try {
|
|
const result = await clickTool.handler({ x: 100, y: 100 });
|
|
expect(result).toHaveProperty('content');
|
|
} catch (error) {
|
|
// Acceptable if AutoGUI implementation is not available
|
|
expect(error).toBeDefined();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Keyboard interaction tools', () => {
|
|
test('should have typing tool', () => {
|
|
const typeTool = autoguiTools.find(tool =>
|
|
tool.name.includes('type') || tool.description.toLowerCase().includes('type')
|
|
);
|
|
expect(typeTool).toBeDefined();
|
|
});
|
|
|
|
test('should validate text input', () => {
|
|
const typeTool = autoguiTools.find(tool =>
|
|
tool.name.includes('type') || tool.description.toLowerCase().includes('type')
|
|
);
|
|
|
|
if (typeTool) {
|
|
const schema = typeTool.inputSchema;
|
|
expect(schema.properties).toHaveProperty('text');
|
|
expect(schema.required).toContain('text');
|
|
}
|
|
});
|
|
|
|
test('should handle typing actions', async () => {
|
|
const typeTool = autoguiTools.find(tool =>
|
|
tool.name.includes('type') || tool.description.toLowerCase().includes('type')
|
|
);
|
|
|
|
if (typeTool) {
|
|
try {
|
|
const result = await typeTool.handler({ text: 'Hello World' });
|
|
expect(result).toHaveProperty('content');
|
|
} catch (error) {
|
|
// Acceptable if AutoGUI implementation is not available
|
|
expect(error).toBeDefined();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Error handling', () => {
|
|
test('should handle AutoGUI initialization failures', async () => {
|
|
const { createAutoGUI } = factory;
|
|
createAutoGUI.mockRejectedValue(new Error('AutoGUI not available'));
|
|
|
|
const statusTool = autoguiTools.find(tool => tool.name === 'autogui_status');
|
|
|
|
try {
|
|
const result = await statusTool!.handler({});
|
|
// Should handle initialization failure gracefully
|
|
expect(result).toHaveProperty('content');
|
|
} catch (error) {
|
|
// Acceptable for AutoGUI tools when implementation is not available
|
|
expect(error).toBeDefined();
|
|
}
|
|
});
|
|
|
|
test('should handle missing implementations', async () => {
|
|
const { getAvailableAutoGUIImplementations } = factory;
|
|
getAvailableAutoGUIImplementations.mockResolvedValue([]);
|
|
|
|
const statusTool = autoguiTools.find(tool => tool.name === 'autogui_status');
|
|
|
|
try {
|
|
const result = await statusTool!.handler({});
|
|
expect(result).toHaveProperty('content');
|
|
} catch (error) {
|
|
expect(error).toBeDefined();
|
|
}
|
|
});
|
|
|
|
test('should handle invalid coordinates', async () => {
|
|
const clickTool = autoguiTools.find(tool =>
|
|
tool.name.includes('click') || tool.description.toLowerCase().includes('click')
|
|
);
|
|
|
|
if (clickTool) {
|
|
try {
|
|
// Test with negative coordinates
|
|
const result = await clickTool.handler({ x: -1, y: -1 });
|
|
|
|
if (!result.isError) {
|
|
// Tool might handle invalid coordinates gracefully
|
|
expect(result).toHaveProperty('content');
|
|
} else {
|
|
// Or it might return an error, which is also acceptable
|
|
expect(result.isError).toBe(true);
|
|
}
|
|
} catch (error) {
|
|
// Validation error is acceptable
|
|
expect(error).toBeDefined();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Factory integration', () => {
|
|
test('should interact with factory functions', () => {
|
|
const { createAutoGUI, getAvailableAutoGUIImplementations, getAutoGUIImplementationStatus } = factory;
|
|
|
|
// Verify mocks are set up (indicates proper factory integration)
|
|
expect(createAutoGUI).toBeDefined();
|
|
expect(getAvailableAutoGUIImplementations).toBeDefined();
|
|
expect(getAutoGUIImplementationStatus).toBeDefined();
|
|
});
|
|
|
|
test('should cache AutoGUI adapter instance', async () => {
|
|
const statusTool = autoguiTools.find(tool => tool.name === 'autogui_status');
|
|
|
|
if (statusTool) {
|
|
// The status tool calls getAvailableAutoGUIImplementations and
|
|
// getAutoGUIImplementationStatus from the factory, which are mocked.
|
|
// Verify that repeated calls produce consistent results, confirming
|
|
// the factory integration works and mocks are properly wired.
|
|
const result1 = await statusTool.handler({});
|
|
const result2 = await statusTool.handler({});
|
|
|
|
expect(result1.content[0].text).toBe(result2.content[0].text);
|
|
|
|
const { getAvailableAutoGUIImplementations } = factory;
|
|
expect(getAvailableAutoGUIImplementations).toHaveBeenCalled();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Tool categories', () => {
|
|
test('should provide system information tools', () => {
|
|
const systemTools = autoguiTools.filter(tool =>
|
|
tool.name.includes('status') ||
|
|
tool.description.toLowerCase().includes('status') ||
|
|
tool.description.toLowerCase().includes('info')
|
|
);
|
|
|
|
expect(systemTools.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('should provide screen interaction tools', () => {
|
|
const screenTools = autoguiTools.filter(tool =>
|
|
tool.name.includes('screenshot') ||
|
|
tool.name.includes('click') ||
|
|
tool.name.includes('mouse') ||
|
|
tool.description.toLowerCase().includes('screen') ||
|
|
tool.description.toLowerCase().includes('mouse')
|
|
);
|
|
|
|
expect(screenTools.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('should provide keyboard interaction tools', () => {
|
|
const keyboardTools = autoguiTools.filter(tool =>
|
|
tool.name.includes('type') ||
|
|
tool.name.includes('key') ||
|
|
tool.description.toLowerCase().includes('keyboard') ||
|
|
tool.description.toLowerCase().includes('type')
|
|
);
|
|
|
|
expect(keyboardTools.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
}); |