Skip to content

Commit 838cff6

Browse files
committed
fix: validate workflows against node type versions
Conceived by Romuald Członkowski - www.aiadvisors.pl/en
1 parent ee4c07c commit 838cff6

2 files changed

Lines changed: 66 additions & 2 deletions

File tree

src/services/workflow-validator.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -653,10 +653,13 @@ export class WorkflowValidator {
653653
'@version': node.typeVersion || 1,
654654
...node.parameters
655655
};
656+
const properties = nodeInfo.isVersioned && node.typeVersion != null
657+
? this.nodeRepository.getNodeVersion(normalizedType, String(node.typeVersion))?.propertiesSchema || nodeInfo.properties
658+
: nodeInfo.properties;
656659
const nodeValidation = this.nodeValidator.validateWithMode(
657660
node.type,
658661
paramsWithVersion,
659-
nodeInfo.properties || [],
662+
properties || [],
660663
'operation',
661664
profile as any
662665
);
@@ -2684,4 +2687,4 @@ export class WorkflowValidator {
26842687
);
26852688
}
26862689
}
2687-
}
2690+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
import { EnhancedConfigValidator } from '@/services/enhanced-config-validator';
3+
import { WorkflowValidator } from '@/services/workflow-validator';
4+
import type { NodeRepository } from '@/database/node-repository';
5+
6+
vi.mock('@/utils/logger');
7+
8+
describe('WorkflowValidator version-specific schemas', () => {
9+
it('validates a Notion 2.2 database page against its own schema', async () => {
10+
const title = {
11+
displayName: 'Title',
12+
name: 'title',
13+
type: 'string',
14+
default: '',
15+
required: true,
16+
displayOptions: { show: { resource: ['databasePage'], operation: ['create'] } },
17+
};
18+
const properties = [
19+
{ displayName: 'Resource', name: 'resource', type: 'options', default: 'page' },
20+
{ displayName: 'Operation', name: 'operation', type: 'options', default: 'create', displayOptions: { show: { resource: ['databasePage'] } } },
21+
title,
22+
];
23+
const repository = {
24+
getAllNodes: vi.fn(() => []),
25+
getNode: vi.fn((type: string) => type === 'nodes-base.manualTrigger'
26+
? { nodeType: type, displayName: 'Manual Trigger', package: 'n8n-nodes-base', isTrigger: true, isVersioned: false, outputs: ['main'], properties: [] }
27+
: { nodeType: type, displayName: 'Notion', package: 'n8n-nodes-base', version: 3, isVersioned: true, outputs: ['main'], properties }),
28+
getNodeVersion: vi.fn(() => ({ propertiesSchema: properties.map(property => property === title ? { ...property, required: false } : property) })),
29+
} as unknown as NodeRepository;
30+
const validator = new WorkflowValidator(repository, EnhancedConfigValidator);
31+
const workflow = {
32+
name: 'notion-title-expression-repro',
33+
nodes: [
34+
{ parameters: {}, id: 'trigger', name: 'Manual Trigger', type: 'n8n-nodes-base.manualTrigger', position: [0, 0], typeVersion: 1 },
35+
{
36+
parameters: {
37+
resource: 'databasePage',
38+
databaseId: { __rl: true, mode: 'list', value: '00000000-0000-0000-0000-000000000000', cachedResultName: 'example-db' },
39+
propertiesUi: { propertyValues: [
40+
{ key: 'URL|url', urlValue: "={{ 'https://example.com' + $json.path }}" },
41+
{ key: 'title|title', title: '={{ $json.title }}' },
42+
{ key: 'abstract|rich_text', textContent: '={{ $json.abstract }}' },
43+
] },
44+
options: {},
45+
},
46+
id: 'notion',
47+
name: 'Store Page',
48+
type: 'n8n-nodes-base.notion',
49+
position: [220, 0],
50+
typeVersion: 2.2,
51+
},
52+
],
53+
connections: { 'Manual Trigger': { main: [[{ node: 'Store Page', type: 'main', index: 0 }]] } },
54+
};
55+
56+
const result = await validator.validateWorkflow(workflow as any, { profile: 'runtime' });
57+
58+
expect(result.errors).toEqual([]);
59+
expect(repository.getNodeVersion).toHaveBeenCalledWith('nodes-base.notion', '2.2');
60+
});
61+
});

0 commit comments

Comments
 (0)