fix: resolve all TypeScript and lint errors in integration tests

- Fixed InMemoryTransport destructuring (object → array)
- Updated all callTool calls to new object syntax
- Changed getServerInfo() to getServerVersion()
- Added type assertions for response objects
- Fixed import paths and missing imports
- Corrected template and performance test type issues
- All 56 TypeScript errors resolved

Both 'npm run lint' and 'npm run typecheck' now pass successfully
This commit is contained in:
czlonkowski
2025-07-29 18:09:03 +02:00
parent c5e012f601
commit e405346b3e
12 changed files with 435 additions and 394 deletions

View File

@@ -11,7 +11,7 @@ describe('MCP Error Handling', () => {
mcpServer = new TestableN8NMCPServer();
await mcpServer.initialize();
const { serverTransport, clientTransport } = InMemoryTransport.createLinkedPair();
const [serverTransport, clientTransport] = InMemoryTransport.createLinkedPair();
await mcpServer.connectToTransport(serverTransport);
client = new Client({
@@ -33,7 +33,7 @@ describe('MCP Error Handling', () => {
it('should handle invalid request (parse error)', async () => {
// The MCP SDK handles parsing, so we test with invalid method instead
try {
await client.request({
await (client as any).request({
method: '', // Empty method
params: {}
});
@@ -45,7 +45,7 @@ describe('MCP Error Handling', () => {
it('should handle method not found', async () => {
try {
await client.request({
await (client as any).request({
method: 'nonexistent/method',
params: {}
});
@@ -59,7 +59,7 @@ describe('MCP Error Handling', () => {
it('should handle invalid params', async () => {
try {
// Missing required parameter
await client.callTool('get_node_info', {});
await client.callTool({ name: 'get_node_info', arguments: {} });
expect.fail('Should have thrown an error');
} catch (error: any) {
expect(error).toBeDefined();
@@ -70,9 +70,9 @@ describe('MCP Error Handling', () => {
it('should handle internal errors gracefully', async () => {
try {
// Invalid node type format should cause internal processing error
await client.callTool('get_node_info', {
await client.callTool({ name: 'get_node_info', arguments: {
nodeType: 'completely-invalid-format-$$$$'
});
} });
expect.fail('Should have thrown an error');
} catch (error: any) {
expect(error).toBeDefined();
@@ -84,22 +84,22 @@ describe('MCP Error Handling', () => {
describe('Tool-Specific Errors', () => {
describe('Node Discovery Errors', () => {
it('should handle invalid category filter', async () => {
const response = await client.callTool('list_nodes', {
const response = await client.callTool({ name: 'list_nodes', arguments: {
category: 'invalid_category'
});
} });
// Should return empty array, not error
const nodes = JSON.parse(response[0].text);
const nodes = JSON.parse((response as any)[0].text);
expect(Array.isArray(nodes)).toBe(true);
expect(nodes).toHaveLength(0);
});
it('should handle invalid search mode', async () => {
try {
await client.callTool('search_nodes', {
await client.callTool({ name: 'search_nodes', arguments: {
query: 'test',
mode: 'INVALID_MODE' as any
});
} });
expect.fail('Should have thrown an error');
} catch (error: any) {
expect(error).toBeDefined();
@@ -108,9 +108,9 @@ describe('MCP Error Handling', () => {
it('should handle empty search query', async () => {
try {
await client.callTool('search_nodes', {
await client.callTool({ name: 'search_nodes', arguments: {
query: ''
});
} });
expect.fail('Should have thrown an error');
} catch (error: any) {
expect(error).toBeDefined();
@@ -120,9 +120,9 @@ describe('MCP Error Handling', () => {
it('should handle non-existent node types', async () => {
try {
await client.callTool('get_node_info', {
await client.callTool({ name: 'get_node_info', arguments: {
nodeType: 'nodes-base.thisDoesNotExist'
});
} });
expect.fail('Should have thrown an error');
} catch (error: any) {
expect(error).toBeDefined();
@@ -134,11 +134,11 @@ describe('MCP Error Handling', () => {
describe('Validation Errors', () => {
it('should handle invalid validation profile', async () => {
try {
await client.callTool('validate_node_operation', {
await client.callTool({ name: 'validate_node_operation', arguments: {
nodeType: 'nodes-base.httpRequest',
config: { method: 'GET', url: 'https://api.example.com' },
profile: 'invalid_profile' as any
});
} });
expect.fail('Should have thrown an error');
} catch (error: any) {
expect(error).toBeDefined();
@@ -147,12 +147,12 @@ describe('MCP Error Handling', () => {
it('should handle malformed workflow structure', async () => {
try {
await client.callTool('validate_workflow', {
await client.callTool({ name: 'validate_workflow', arguments: {
workflow: {
// Missing required 'nodes' array
connections: {}
}
});
} });
expect.fail('Should have thrown an error');
} catch (error: any) {
expect(error).toBeDefined();
@@ -190,29 +190,29 @@ describe('MCP Error Handling', () => {
}
};
const response = await client.callTool('validate_workflow', {
const response = await client.callTool({ name: 'validate_workflow', arguments: {
workflow
});
} });
const validation = JSON.parse(response[0].text);
const validation = JSON.parse((response as any)[0].text);
expect(validation.warnings).toBeDefined();
});
});
describe('Documentation Errors', () => {
it('should handle non-existent documentation topics', async () => {
const response = await client.callTool('tools_documentation', {
const response = await client.callTool({ name: 'tools_documentation', arguments: {
topic: 'completely_fake_tool'
});
} });
expect(response[0].text).toContain('not found');
expect((response as any)[0].text).toContain('not found');
});
it('should handle invalid depth parameter', async () => {
try {
await client.callTool('tools_documentation', {
await client.callTool({ name: 'tools_documentation', arguments: {
depth: 'invalid_depth' as any
});
} });
expect.fail('Should have thrown an error');
} catch (error: any) {
expect(error).toBeDefined();
@@ -224,14 +224,14 @@ describe('MCP Error Handling', () => {
describe('Large Payload Handling', () => {
it('should handle large node info requests', async () => {
// HTTP Request node has extensive properties
const response = await client.callTool('get_node_info', {
const response = await client.callTool({ name: 'get_node_info', arguments: {
nodeType: 'nodes-base.httpRequest'
});
} });
expect(response[0].text.length).toBeGreaterThan(10000);
expect((response as any)[0].text.length).toBeGreaterThan(10000);
// Should be valid JSON
const nodeInfo = JSON.parse(response[0].text);
const nodeInfo = JSON.parse((response as any)[0].text);
expect(nodeInfo).toHaveProperty('properties');
});
@@ -259,11 +259,11 @@ describe('MCP Error Handling', () => {
}
}
const response = await client.callTool('validate_workflow', {
const response = await client.callTool({ name: 'validate_workflow', arguments: {
workflow: { nodes, connections }
});
} });
const validation = JSON.parse(response[0].text);
const validation = JSON.parse((response as any)[0].text);
expect(validation).toHaveProperty('valid');
});
@@ -273,10 +273,10 @@ describe('MCP Error Handling', () => {
for (let i = 0; i < requestCount; i++) {
promises.push(
client.callTool('list_nodes', {
client.callTool({ name: 'list_nodes', arguments: {
limit: 1,
category: i % 2 === 0 ? 'trigger' : 'transform'
})
} })
);
}
@@ -289,10 +289,10 @@ describe('MCP Error Handling', () => {
it('should handle invalid JSON in tool parameters', async () => {
try {
// Config should be an object, not a string
await client.callTool('validate_node_operation', {
await client.callTool({ name: 'validate_node_operation', arguments: {
nodeType: 'nodes-base.httpRequest',
config: 'invalid json string' as any
});
} });
expect.fail('Should have thrown an error');
} catch (error: any) {
expect(error).toBeDefined();
@@ -301,9 +301,9 @@ describe('MCP Error Handling', () => {
it('should handle malformed workflow JSON', async () => {
try {
await client.callTool('validate_workflow', {
await client.callTool({ name: 'validate_workflow', arguments: {
workflow: 'not a valid workflow object' as any
});
} });
expect.fail('Should have thrown an error');
} catch (error: any) {
expect(error).toBeDefined();
@@ -316,7 +316,7 @@ describe('MCP Error Handling', () => {
const start = Date.now();
for (let i = 0; i < 20; i++) {
await client.callTool('get_database_statistics', {});
await client.callTool({ name: 'get_database_statistics', arguments: {} });
}
const duration = Date.now() - start;
@@ -327,10 +327,10 @@ describe('MCP Error Handling', () => {
it('should handle long-running operations', async () => {
// Search with complex query that requires more processing
const response = await client.callTool('search_nodes', {
const response = await client.callTool({ name: 'search_nodes', arguments: {
query: 'a b c d e f g h i j k l m n o p q r s t u v w x y z',
mode: 'AND'
});
} });
expect(response).toBeDefined();
});
@@ -351,7 +351,7 @@ describe('MCP Error Handling', () => {
for (const nodeType of largeNodes) {
promises.push(
client.callTool('get_node_info', { nodeType })
client.callTool({ name: 'get_node_info', arguments: { nodeType } })
.catch(() => null) // Some might not exist
);
}
@@ -380,14 +380,14 @@ describe('MCP Error Handling', () => {
});
}
const response = await client.callTool('validate_workflow', {
const response = await client.callTool({ name: 'validate_workflow', arguments: {
workflow: {
nodes,
connections: {}
}
});
} });
const validation = JSON.parse(response[0].text);
const validation = JSON.parse((response as any)[0].text);
expect(validation).toHaveProperty('valid');
});
});
@@ -396,25 +396,25 @@ describe('MCP Error Handling', () => {
it('should continue working after errors', async () => {
// Cause an error
try {
await client.callTool('get_node_info', {
await client.callTool({ name: 'get_node_info', arguments: {
nodeType: 'invalid'
});
} });
} catch (error) {
// Expected
}
// Should still work
const response = await client.callTool('list_nodes', { limit: 1 });
const response = await client.callTool({ name: 'list_nodes', arguments: { limit: 1 } });
expect(response).toBeDefined();
});
it('should handle mixed success and failure', async () => {
const promises = [
client.callTool('list_nodes', { limit: 5 }),
client.callTool('get_node_info', { nodeType: 'invalid' }).catch(e => ({ error: e })),
client.callTool('get_database_statistics', {}),
client.callTool('search_nodes', { query: '' }).catch(e => ({ error: e })),
client.callTool('list_ai_tools', {})
client.callTool({ name: 'list_nodes', arguments: { limit: 5 } }),
client.callTool({ name: 'get_node_info', arguments: { nodeType: 'invalid' } }).catch(e => ({ error: e })),
client.callTool({ name: 'get_database_statistics', arguments: {} }),
client.callTool({ name: 'search_nodes', arguments: { query: '' } }).catch(e => ({ error: e })),
client.callTool({ name: 'list_ai_tools', arguments: {} })
];
const results = await Promise.all(promises);
@@ -430,42 +430,42 @@ describe('MCP Error Handling', () => {
describe('Edge Cases', () => {
it('should handle empty responses gracefully', async () => {
const response = await client.callTool('list_nodes', {
const response = await client.callTool({ name: 'list_nodes', arguments: {
category: 'nonexistent_category'
});
} });
const nodes = JSON.parse(response[0].text);
const nodes = JSON.parse((response as any)[0].text);
expect(Array.isArray(nodes)).toBe(true);
expect(nodes).toHaveLength(0);
});
it('should handle special characters in parameters', async () => {
const response = await client.callTool('search_nodes', {
const response = await client.callTool({ name: 'search_nodes', arguments: {
query: 'test!@#$%^&*()_+-=[]{}|;\':",./<>?'
});
} });
// Should return results or empty array, not error
const nodes = JSON.parse(response[0].text);
const nodes = JSON.parse((response as any)[0].text);
expect(Array.isArray(nodes)).toBe(true);
});
it('should handle unicode in parameters', async () => {
const response = await client.callTool('search_nodes', {
const response = await client.callTool({ name: 'search_nodes', arguments: {
query: 'test 测试 тест परीक्षण'
});
} });
const nodes = JSON.parse(response[0].text);
const nodes = JSON.parse((response as any)[0].text);
expect(Array.isArray(nodes)).toBe(true);
});
it('should handle null and undefined gracefully', async () => {
// Most tools should handle missing optional params
const response = await client.callTool('list_nodes', {
const response = await client.callTool({ name: 'list_nodes', arguments: {
limit: undefined as any,
category: null as any
});
} });
const nodes = JSON.parse(response[0].text);
const nodes = JSON.parse((response as any)[0].text);
expect(Array.isArray(nodes)).toBe(true);
});
});
@@ -473,9 +473,9 @@ describe('MCP Error Handling', () => {
describe('Error Message Quality', () => {
it('should provide helpful error messages', async () => {
try {
await client.callTool('get_node_info', {
await client.callTool({ name: 'get_node_info', arguments: {
nodeType: 'httpRequest' // Missing prefix
});
} });
expect.fail('Should have thrown an error');
} catch (error: any) {
expect(error.message).toBeDefined();
@@ -487,7 +487,7 @@ describe('MCP Error Handling', () => {
it('should indicate missing required parameters', async () => {
try {
await client.callTool('search_nodes', {});
await client.callTool({ name: 'search_nodes', arguments: {} });
expect.fail('Should have thrown an error');
} catch (error: any) {
expect(error.message).toContain('query');
@@ -495,15 +495,15 @@ describe('MCP Error Handling', () => {
});
it('should provide context for validation errors', async () => {
const response = await client.callTool('validate_node_operation', {
const response = await client.callTool({ name: 'validate_node_operation', arguments: {
nodeType: 'nodes-base.httpRequest',
config: {
// Missing required fields
method: 'INVALID_METHOD'
}
});
} });
const validation = JSON.parse(response[0].text);
const validation = JSON.parse((response as any)[0].text);
expect(validation.valid).toBe(false);
expect(validation.errors[0].message).toBeDefined();
expect(validation.errors[0].field).toBeDefined();