- 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>
238 lines
6.2 KiB
TypeScript
238 lines
6.2 KiB
TypeScript
import { bench, describe } from 'vitest';
|
|
import { ConfigValidator } from '../../src/services/config-validator';
|
|
import { EnhancedConfigValidator } from '../../src/services/enhanced-config-validator';
|
|
import { ExpressionValidator } from '../../src/services/expression-validator';
|
|
import { WorkflowValidator } from '../../src/services/workflow-validator';
|
|
import { NodeRepository } from '../../src/database/node-repository';
|
|
import { SQLiteStorageService } from '../../src/services/sqlite-storage-service';
|
|
import { NodeLoader } from '../../src/loaders/node-loader';
|
|
|
|
describe('Validation Performance', () => {
|
|
let validator: ConfigValidator;
|
|
let enhancedValidator: EnhancedConfigValidator;
|
|
let expressionValidator: ExpressionValidator;
|
|
let workflowValidator: WorkflowValidator;
|
|
let repository: NodeRepository;
|
|
let storage: SQLiteStorageService;
|
|
|
|
const simpleConfig = {
|
|
url: 'https://api.example.com',
|
|
method: 'GET',
|
|
authentication: 'none'
|
|
};
|
|
|
|
const complexConfig = {
|
|
resource: 'message',
|
|
operation: 'send',
|
|
channel: 'C1234567890',
|
|
text: 'Hello from benchmark',
|
|
authentication: {
|
|
type: 'oAuth2',
|
|
credentials: {
|
|
oauthTokenData: {
|
|
access_token: 'xoxb-test-token'
|
|
}
|
|
}
|
|
},
|
|
options: {
|
|
as_user: true,
|
|
link_names: true,
|
|
parse: 'full',
|
|
reply_broadcast: false,
|
|
thread_ts: '',
|
|
unfurl_links: true,
|
|
unfurl_media: true
|
|
}
|
|
};
|
|
|
|
const simpleWorkflow = {
|
|
name: 'Simple Workflow',
|
|
nodes: [
|
|
{
|
|
id: '1',
|
|
name: 'Manual Trigger',
|
|
type: 'n8n-nodes-base.manualTrigger',
|
|
typeVersion: 1,
|
|
position: [250, 300],
|
|
parameters: {}
|
|
},
|
|
{
|
|
id: '2',
|
|
name: 'HTTP Request',
|
|
type: 'n8n-nodes-base.httpRequest',
|
|
typeVersion: 4.2,
|
|
position: [450, 300],
|
|
parameters: {
|
|
url: 'https://api.example.com',
|
|
method: 'GET'
|
|
}
|
|
}
|
|
],
|
|
connections: {
|
|
'1': {
|
|
main: [
|
|
[
|
|
{
|
|
node: '2',
|
|
type: 'main',
|
|
index: 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
}
|
|
};
|
|
|
|
const complexWorkflow = {
|
|
name: 'Complex Workflow',
|
|
nodes: Array.from({ length: 20 }, (_, i) => ({
|
|
id: `${i + 1}`,
|
|
name: `Node ${i + 1}`,
|
|
type: i % 3 === 0 ? 'n8n-nodes-base.httpRequest' :
|
|
i % 3 === 1 ? 'n8n-nodes-base.slack' :
|
|
'n8n-nodes-base.code',
|
|
typeVersion: 1,
|
|
position: [250 + (i % 5) * 200, 300 + Math.floor(i / 5) * 150],
|
|
parameters: {
|
|
url: '={{ $json.url }}',
|
|
method: 'POST',
|
|
body: '={{ JSON.stringify($json) }}',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}
|
|
})),
|
|
connections: Object.fromEntries(
|
|
Array.from({ length: 19 }, (_, i) => [
|
|
`${i + 1}`,
|
|
{
|
|
main: [[{ node: `${i + 2}`, type: 'main', index: 0 }]]
|
|
}
|
|
])
|
|
)
|
|
};
|
|
|
|
beforeAll(async () => {
|
|
storage = new SQLiteStorageService(':memory:');
|
|
repository = new NodeRepository(storage);
|
|
const loader = new NodeLoader(repository);
|
|
await loader.loadPackage('n8n-nodes-base');
|
|
|
|
validator = new ConfigValidator(repository);
|
|
enhancedValidator = new EnhancedConfigValidator(repository);
|
|
expressionValidator = new ExpressionValidator();
|
|
workflowValidator = new WorkflowValidator(repository);
|
|
});
|
|
|
|
afterAll(() => {
|
|
storage.close();
|
|
});
|
|
|
|
bench('validateNode - simple config minimal', async () => {
|
|
await validator.validateNode('n8n-nodes-base.httpRequest', simpleConfig, 'minimal');
|
|
}, {
|
|
iterations: 1000,
|
|
warmupIterations: 100,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('validateNode - simple config strict', async () => {
|
|
await validator.validateNode('n8n-nodes-base.httpRequest', simpleConfig, 'strict');
|
|
}, {
|
|
iterations: 500,
|
|
warmupIterations: 50,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('validateNode - complex config', async () => {
|
|
await enhancedValidator.validateNode('n8n-nodes-base.slack', complexConfig, 'ai-friendly');
|
|
}, {
|
|
iterations: 500,
|
|
warmupIterations: 50,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('validateMinimal - missing fields check', async () => {
|
|
await validator.validateMinimal('n8n-nodes-base.httpRequest', {});
|
|
}, {
|
|
iterations: 2000,
|
|
warmupIterations: 200,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('validateExpression - simple expression', async () => {
|
|
expressionValidator.validateExpression('{{ $json.data }}');
|
|
}, {
|
|
iterations: 5000,
|
|
warmupIterations: 500,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('validateExpression - complex expression', async () => {
|
|
expressionValidator.validateExpression('{{ $node["HTTP Request"].json.items.map(item => item.id).join(",") }}');
|
|
}, {
|
|
iterations: 2000,
|
|
warmupIterations: 200,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('validateWorkflow - simple workflow', async () => {
|
|
await workflowValidator.validateWorkflow(simpleWorkflow);
|
|
}, {
|
|
iterations: 500,
|
|
warmupIterations: 50,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('validateWorkflow - complex workflow', async () => {
|
|
await workflowValidator.validateWorkflow(complexWorkflow);
|
|
}, {
|
|
iterations: 100,
|
|
warmupIterations: 10,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('validateConnections - simple', async () => {
|
|
workflowValidator.validateConnections(simpleWorkflow);
|
|
}, {
|
|
iterations: 2000,
|
|
warmupIterations: 200,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('validateConnections - complex', async () => {
|
|
workflowValidator.validateConnections(complexWorkflow);
|
|
}, {
|
|
iterations: 500,
|
|
warmupIterations: 50,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('validateExpressions - workflow with many expressions', async () => {
|
|
workflowValidator.validateExpressions(complexWorkflow);
|
|
}, {
|
|
iterations: 200,
|
|
warmupIterations: 20,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
|
|
bench('getPropertyDependencies', async () => {
|
|
await enhancedValidator.getPropertyDependencies('n8n-nodes-base.httpRequest');
|
|
}, {
|
|
iterations: 1000,
|
|
warmupIterations: 100,
|
|
warmupTime: 500,
|
|
time: 3000
|
|
});
|
|
}); |