mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-06 05:23:08 +00:00
test: Add comprehensive edge case coverage for telemetry quick wins
Added 20 edge case tests based on code review recommendations: **Duplicate ID Validation (4 tests)**: - Multiple duplicate IDs (3+ nodes with same ID) - Duplicate IDs with same node type - Duplicate IDs with empty/null node names - Duplicate IDs with missing node properties **AI Agent Validator (16 tests)**: maxIterations edge cases (7 tests): - Boundary values: 0 (reject), 1 (accept), 51 (warn), MAX_SAFE_INTEGER (warn) - Invalid types: NaN (reject), negative decimal (reject) - Threshold testing: 50 vs 51 promptType validation (4 tests): - Whitespace-only text (reject) - Very long text 3200+ chars (accept) - undefined/null text (reject) System message validation (5 tests): - Empty/whitespace messages (suggest adding) - Very long messages >1000 chars (accept) - Special characters, emojis, unicode (accept) - Multi-line formatting (accept) - Boundary: 19 chars (warn), 20 chars (accept) **Test Quality Improvements**: - Fixed flaky system message test (changed from expect.stringContaining to .some()) - All tests are deterministic - Comprehensive inline comments - Follows existing test patterns All 20 new tests passing. Zero regressions. Concieved by Romuald Członkowski - www.aiadvisors.pl/en
This commit is contained in:
@@ -353,5 +353,222 @@ describe('WorkflowValidator', () => {
|
||||
// This validates that our implementation uses the pattern
|
||||
expect(expectedPattern.test('crypto.randomUUID()')).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect multiple nodes with the same duplicate ID', () => {
|
||||
// Edge case: Three or more nodes with the same ID
|
||||
const workflow = {
|
||||
name: 'Test Workflow with Multiple Duplicates',
|
||||
nodes: [
|
||||
{
|
||||
id: 'shared-id',
|
||||
name: 'First Node',
|
||||
type: 'n8n-nodes-base.httpRequest',
|
||||
typeVersion: 3,
|
||||
position: [250, 300],
|
||||
parameters: {}
|
||||
},
|
||||
{
|
||||
id: 'shared-id', // Duplicate 1
|
||||
name: 'Second Node',
|
||||
type: 'n8n-nodes-base.set',
|
||||
typeVersion: 2,
|
||||
position: [450, 300],
|
||||
parameters: {}
|
||||
},
|
||||
{
|
||||
id: 'shared-id', // Duplicate 2
|
||||
name: 'Third Node',
|
||||
type: 'n8n-nodes-base.code',
|
||||
typeVersion: 1,
|
||||
position: [650, 300],
|
||||
parameters: {}
|
||||
}
|
||||
],
|
||||
connections: {}
|
||||
};
|
||||
|
||||
// Simulate validation logic
|
||||
const nodeIds = new Set<string>();
|
||||
const nodeIdToIndex = new Map<string, number>();
|
||||
const errors: Array<{ message: string }> = [];
|
||||
|
||||
for (let i = 0; i < workflow.nodes.length; i++) {
|
||||
const node = workflow.nodes[i];
|
||||
if (nodeIds.has(node.id)) {
|
||||
const firstNodeIndex = nodeIdToIndex.get(node.id);
|
||||
const firstNode = firstNodeIndex !== undefined ? workflow.nodes[firstNodeIndex] : undefined;
|
||||
|
||||
errors.push({
|
||||
message: `Duplicate node ID: "${node.id}". Node at index ${i} (name: "${node.name}", type: "${node.type}") conflicts with node at index ${firstNodeIndex} (name: "${firstNode?.name || 'unknown'}", type: "${firstNode?.type || 'unknown'}")`
|
||||
});
|
||||
} else {
|
||||
nodeIds.add(node.id);
|
||||
nodeIdToIndex.set(node.id, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Should report 2 errors (nodes at index 1 and 2 both conflict with node at index 0)
|
||||
expect(errors).toHaveLength(2);
|
||||
expect(errors[0].message).toContain('index 1');
|
||||
expect(errors[0].message).toContain('Second Node');
|
||||
expect(errors[1].message).toContain('index 2');
|
||||
expect(errors[1].message).toContain('Third Node');
|
||||
});
|
||||
|
||||
it('should handle duplicate IDs with same node type', () => {
|
||||
// Edge case: Both nodes are the same type
|
||||
const workflow = {
|
||||
name: 'Test Workflow with Same Type Duplicates',
|
||||
nodes: [
|
||||
{
|
||||
id: 'duplicate-slack',
|
||||
name: 'Slack Send 1',
|
||||
type: 'n8n-nodes-base.slack',
|
||||
typeVersion: 2,
|
||||
position: [250, 300],
|
||||
parameters: {}
|
||||
},
|
||||
{
|
||||
id: 'duplicate-slack',
|
||||
name: 'Slack Send 2',
|
||||
type: 'n8n-nodes-base.slack',
|
||||
typeVersion: 2,
|
||||
position: [450, 300],
|
||||
parameters: {}
|
||||
}
|
||||
],
|
||||
connections: {}
|
||||
};
|
||||
|
||||
// Simulate validation logic
|
||||
const nodeIds = new Set<string>();
|
||||
const nodeIdToIndex = new Map<string, number>();
|
||||
const errors: Array<{ message: string }> = [];
|
||||
|
||||
for (let i = 0; i < workflow.nodes.length; i++) {
|
||||
const node = workflow.nodes[i];
|
||||
if (nodeIds.has(node.id)) {
|
||||
const firstNodeIndex = nodeIdToIndex.get(node.id);
|
||||
const firstNode = firstNodeIndex !== undefined ? workflow.nodes[firstNodeIndex] : undefined;
|
||||
|
||||
errors.push({
|
||||
message: `Duplicate node ID: "${node.id}". Node at index ${i} (name: "${node.name}", type: "${node.type}") conflicts with node at index ${firstNodeIndex} (name: "${firstNode?.name || 'unknown'}", type: "${firstNode?.type || 'unknown'}")`
|
||||
});
|
||||
} else {
|
||||
nodeIds.add(node.id);
|
||||
nodeIdToIndex.set(node.id, i);
|
||||
}
|
||||
}
|
||||
|
||||
expect(errors).toHaveLength(1);
|
||||
expect(errors[0].message).toContain('Duplicate node ID: "duplicate-slack"');
|
||||
expect(errors[0].message).toContain('Slack Send 2');
|
||||
expect(errors[0].message).toContain('Slack Send 1');
|
||||
// Both should show the same type
|
||||
expect(errors[0].message).toMatch(/n8n-nodes-base\.slack.*n8n-nodes-base\.slack/s);
|
||||
});
|
||||
|
||||
it('should handle duplicate IDs with empty node names gracefully', () => {
|
||||
// Edge case: Empty string node names
|
||||
const workflow = {
|
||||
name: 'Test Workflow with Empty Names',
|
||||
nodes: [
|
||||
{
|
||||
id: 'empty-name-id',
|
||||
name: '',
|
||||
type: 'n8n-nodes-base.httpRequest',
|
||||
typeVersion: 3,
|
||||
position: [250, 300],
|
||||
parameters: {}
|
||||
},
|
||||
{
|
||||
id: 'empty-name-id',
|
||||
name: '',
|
||||
type: 'n8n-nodes-base.set',
|
||||
typeVersion: 2,
|
||||
position: [450, 300],
|
||||
parameters: {}
|
||||
}
|
||||
],
|
||||
connections: {}
|
||||
};
|
||||
|
||||
// Simulate validation logic with safe fallback
|
||||
const nodeIds = new Set<string>();
|
||||
const nodeIdToIndex = new Map<string, number>();
|
||||
const errors: Array<{ message: string }> = [];
|
||||
|
||||
for (let i = 0; i < workflow.nodes.length; i++) {
|
||||
const node = workflow.nodes[i];
|
||||
if (nodeIds.has(node.id)) {
|
||||
const firstNodeIndex = nodeIdToIndex.get(node.id);
|
||||
const firstNode = firstNodeIndex !== undefined ? workflow.nodes[firstNodeIndex] : undefined;
|
||||
|
||||
errors.push({
|
||||
message: `Duplicate node ID: "${node.id}". Node at index ${i} (name: "${node.name}", type: "${node.type}") conflicts with node at index ${firstNodeIndex} (name: "${firstNode?.name || 'unknown'}", type: "${firstNode?.type || 'unknown'}")`
|
||||
});
|
||||
} else {
|
||||
nodeIds.add(node.id);
|
||||
nodeIdToIndex.set(node.id, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Should not crash and should use empty string in message
|
||||
expect(errors).toHaveLength(1);
|
||||
expect(errors[0].message).toContain('Duplicate node ID');
|
||||
expect(errors[0].message).toContain('name: ""');
|
||||
});
|
||||
|
||||
it('should handle duplicate IDs with missing node properties', () => {
|
||||
// Edge case: Node with undefined type or name
|
||||
const workflow = {
|
||||
name: 'Test Workflow with Missing Properties',
|
||||
nodes: [
|
||||
{
|
||||
id: 'missing-props',
|
||||
name: 'Valid Node',
|
||||
type: 'n8n-nodes-base.httpRequest',
|
||||
typeVersion: 3,
|
||||
position: [250, 300],
|
||||
parameters: {}
|
||||
},
|
||||
{
|
||||
id: 'missing-props',
|
||||
name: undefined as any,
|
||||
type: undefined as any,
|
||||
typeVersion: 2,
|
||||
position: [450, 300],
|
||||
parameters: {}
|
||||
}
|
||||
],
|
||||
connections: {}
|
||||
};
|
||||
|
||||
// Simulate validation logic with safe fallbacks
|
||||
const nodeIds = new Set<string>();
|
||||
const nodeIdToIndex = new Map<string, number>();
|
||||
const errors: Array<{ message: string }> = [];
|
||||
|
||||
for (let i = 0; i < workflow.nodes.length; i++) {
|
||||
const node = workflow.nodes[i];
|
||||
if (nodeIds.has(node.id)) {
|
||||
const firstNodeIndex = nodeIdToIndex.get(node.id);
|
||||
const firstNode = firstNodeIndex !== undefined ? workflow.nodes[firstNodeIndex] : undefined;
|
||||
|
||||
errors.push({
|
||||
message: `Duplicate node ID: "${node.id}". Node at index ${i} (name: "${node.name}", type: "${node.type}") conflicts with node at index ${firstNodeIndex} (name: "${firstNode?.name || 'unknown'}", type: "${firstNode?.type || 'unknown'}")`
|
||||
});
|
||||
} else {
|
||||
nodeIds.add(node.id);
|
||||
nodeIdToIndex.set(node.id, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Should use fallback values without crashing
|
||||
expect(errors).toHaveLength(1);
|
||||
expect(errors[0].message).toContain('Duplicate node ID: "missing-props"');
|
||||
expect(errors[0].message).toContain('name: "undefined"');
|
||||
expect(errors[0].message).toContain('type: "undefined"');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user