Files
n8n-mcp/tests/unit/__mocks__/n8n-nodes-base.test.ts
czlonkowski b5210e5963 feat: add comprehensive performance benchmark tracking system
- Create benchmark test suites for critical operations:
  - Node loading performance
  - Database query performance
  - Search operations performance
  - Validation performance
  - MCP tool execution performance

- Add GitHub Actions workflow for benchmark tracking:
  - Runs on push to main and PRs
  - Uses github-action-benchmark for historical tracking
  - Comments on PRs with performance results
  - Alerts on >10% performance regressions
  - Stores results in GitHub Pages

- Create benchmark infrastructure:
  - Custom Vitest benchmark configuration
  - JSON reporter for CI results
  - Result formatter for github-action-benchmark
  - Performance threshold documentation

- Add supporting utilities:
  - SQLiteStorageService for benchmark database setup
  - MCPEngine wrapper for testing MCP tools
  - Test factories for generating benchmark data
  - Enhanced NodeRepository with benchmark methods

- Document benchmark system:
  - Comprehensive benchmark guide in docs/BENCHMARKS.md
  - Performance thresholds in .github/BENCHMARK_THRESHOLDS.md
  - README for benchmarks directory
  - Integration with existing test suite

The benchmark system will help monitor performance over time and catch regressions before they reach production.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 22:45:09 +02:00

224 lines
7.0 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { getNodeTypes, mockNodeBehavior, resetAllMocks, registerMockNode } from './n8n-nodes-base';
describe('n8n-nodes-base mock', () => {
beforeEach(() => {
resetAllMocks();
});
describe('getNodeTypes', () => {
it('should return node types registry', () => {
const registry = getNodeTypes();
expect(registry).toBeDefined();
expect(registry.getByName).toBeDefined();
expect(registry.getByNameAndVersion).toBeDefined();
});
it('should retrieve webhook node', () => {
const registry = getNodeTypes();
const webhookNode = registry.getByName('webhook');
expect(webhookNode).toBeDefined();
expect(webhookNode?.description.name).toBe('webhook');
expect(webhookNode?.description.group).toContain('trigger');
expect(webhookNode?.webhook).toBeDefined();
});
it('should retrieve httpRequest node', () => {
const registry = getNodeTypes();
const httpNode = registry.getByName('httpRequest');
expect(httpNode).toBeDefined();
expect(httpNode?.description.name).toBe('httpRequest');
expect(httpNode?.description.version).toBe(3);
expect(httpNode?.execute).toBeDefined();
});
it('should retrieve slack node', () => {
const registry = getNodeTypes();
const slackNode = registry.getByName('slack');
expect(slackNode).toBeDefined();
expect(slackNode?.description.credentials).toHaveLength(1);
expect(slackNode?.description.credentials?.[0].name).toBe('slackApi');
});
});
describe('node execution', () => {
it('should execute webhook node', async () => {
const registry = getNodeTypes();
const webhookNode = registry.getByName('webhook');
const mockContext = {
getWebhookName: vi.fn(() => 'default'),
getBodyData: vi.fn(() => ({ test: 'data' })),
getHeaderData: vi.fn(() => ({ 'content-type': 'application/json' })),
getQueryData: vi.fn(() => ({ query: 'param' })),
getRequestObject: vi.fn(),
getResponseObject: vi.fn(),
helpers: {
returnJsonArray: vi.fn((data) => [{ json: data }]),
},
};
const result = await webhookNode?.webhook?.call(mockContext);
expect(result).toBeDefined();
expect(result.workflowData).toBeDefined();
expect(result.workflowData[0]).toHaveLength(1);
expect(result.workflowData[0][0].json).toMatchObject({
headers: { 'content-type': 'application/json' },
params: { query: 'param' },
body: { test: 'data' },
});
});
it('should execute httpRequest node', async () => {
const registry = getNodeTypes();
const httpNode = registry.getByName('httpRequest');
const mockContext = {
getInputData: vi.fn(() => [{ json: { test: 'input' } }]),
getNodeParameter: vi.fn((name: string) => {
if (name === 'method') return 'POST';
if (name === 'url') return 'https://api.example.com';
return '';
}),
helpers: {
returnJsonArray: vi.fn((data) => [{ json: data }]),
httpRequest: vi.fn(),
},
};
const result = await httpNode?.execute?.call(mockContext);
expect(result).toBeDefined();
expect(result).toHaveLength(1);
expect(result[0]).toHaveLength(1);
expect(result[0][0].json).toMatchObject({
statusCode: 200,
body: {
success: true,
method: 'POST',
url: 'https://api.example.com',
},
});
});
});
describe('mockNodeBehavior', () => {
it('should override node execution behavior', async () => {
const customExecute = vi.fn(async function() {
return [[{ json: { custom: 'response' } }]];
});
mockNodeBehavior('httpRequest', {
execute: customExecute,
});
const registry = getNodeTypes();
const httpNode = registry.getByName('httpRequest');
const mockContext = {
getInputData: vi.fn(() => []),
getNodeParameter: vi.fn(),
};
const result = await httpNode?.execute?.call(mockContext);
expect(customExecute).toHaveBeenCalled();
expect(result).toEqual([[{ json: { custom: 'response' } }]]);
});
it('should override node description', () => {
mockNodeBehavior('slack', {
description: {
displayName: 'Custom Slack',
version: 3,
},
});
const registry = getNodeTypes();
const slackNode = registry.getByName('slack');
expect(slackNode?.description.displayName).toBe('Custom Slack');
expect(slackNode?.description.version).toBe(3);
expect(slackNode?.description.name).toBe('slack'); // Original preserved
});
});
describe('registerMockNode', () => {
it('should register custom node', () => {
const customNode = {
description: {
displayName: 'Custom Node',
name: 'customNode',
group: ['transform'],
version: 1,
description: 'A custom test node',
defaults: { name: 'Custom' },
inputs: ['main'],
outputs: ['main'],
properties: [],
},
execute: vi.fn(async function() {
return [[{ json: { custom: true } }]];
}),
};
registerMockNode('customNode', customNode);
const registry = getNodeTypes();
const retrievedNode = registry.getByName('customNode');
expect(retrievedNode).toBe(customNode);
expect(retrievedNode?.description.name).toBe('customNode');
});
});
describe('conditional nodes', () => {
it('should execute if node with two outputs', async () => {
const registry = getNodeTypes();
const ifNode = registry.getByName('if');
const mockContext = {
getInputData: vi.fn(() => [
{ json: { value: 1 } },
{ json: { value: 2 } },
{ json: { value: 3 } },
{ json: { value: 4 } },
]),
getNodeParameter: vi.fn(),
};
const result = await ifNode?.execute?.call(mockContext);
expect(result).toHaveLength(2); // true and false outputs
expect(result[0]).toHaveLength(2); // even indices
expect(result[1]).toHaveLength(2); // odd indices
});
it('should execute switch node with multiple outputs', async () => {
const registry = getNodeTypes();
const switchNode = registry.getByName('switch');
const mockContext = {
getInputData: vi.fn(() => [
{ json: { value: 1 } },
{ json: { value: 2 } },
{ json: { value: 3 } },
{ json: { value: 4 } },
]),
getNodeParameter: vi.fn(),
};
const result = await switchNode?.execute?.call(mockContext);
expect(result).toHaveLength(4); // 4 outputs
expect(result[0]).toHaveLength(1); // item 0
expect(result[1]).toHaveLength(1); // item 1
expect(result[2]).toHaveLength(1); // item 2
expect(result[3]).toHaveLength(1); // item 3
});
});
});