feat: add TypeScript type safety with strategic any assertions (v2.17.5)

Added comprehensive TypeScript type definitions for n8n node parsing while
maintaining zero compilation errors. Uses pragmatic "70% benefit with 0%
breakage" approach with strategic `any` assertions.

## Type Definitions (src/types/node-types.ts)
- NodeClass union type replaces `any` in method signatures
- Type guards: isVersionedNodeInstance(), isVersionedNodeClass()
- Utility functions for safe node handling

## Parser Updates
- node-parser.ts: All methods use NodeClass (15+ methods)
- simple-parser.ts: Strongly typed method signatures
- property-extractor.ts: Typed extraction methods
- 30+ method signatures improved

## Strategic Pattern
- Strong types in public method signatures (caller type safety)
- Strategic `as any` assertions for internal union type access
- Pattern: const desc = description as any; // Access union properties

## Benefits
- Better IDE support and auto-complete
- Compile-time safety at call sites
- Type-based documentation
- Zero compilation errors
- Bug prevention (would have caught v2.17.4 baseDescription issue)

## Test Updates
- All test files updated with `as any` for mock objects
- Zero compilation errors maintained

## Known Limitations
- ~70% type coverage (signatures typed, internal logic uses assertions)
- Union types (INodeTypeBaseDescription vs INodeTypeDescription) not fully resolved
- Future work: Conditional types or overloads for 100% type safety

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
czlonkowski
2025-10-07 22:16:59 +02:00
parent 8e2e1dce62
commit f3164e202f
15 changed files with 1293 additions and 314 deletions

View File

@@ -41,7 +41,7 @@ describe('NodeParser - Output Extraction', () => {
description = nodeDescription;
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.outputs).toEqual(outputs);
expect(result.outputNames).toBeUndefined();
@@ -60,7 +60,7 @@ describe('NodeParser - Output Extraction', () => {
description = nodeDescription;
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.outputNames).toEqual(outputNames);
expect(result.outputs).toBeUndefined();
@@ -84,7 +84,7 @@ describe('NodeParser - Output Extraction', () => {
description = nodeDescription;
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.outputs).toEqual(outputs);
expect(result.outputNames).toEqual(outputNames);
@@ -103,7 +103,7 @@ describe('NodeParser - Output Extraction', () => {
description = nodeDescription;
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.outputs).toEqual([singleOutput]);
});
@@ -119,7 +119,7 @@ describe('NodeParser - Output Extraction', () => {
description = nodeDescription;
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.outputNames).toEqual(['main']);
});
@@ -152,7 +152,7 @@ describe('NodeParser - Output Extraction', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
// Should get outputs from latest version (2)
expect(result.outputs).toEqual(versionedOutputs);
@@ -172,7 +172,7 @@ describe('NodeParser - Output Extraction', () => {
}
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.outputs).toBeUndefined();
expect(result.outputNames).toBeUndefined();
@@ -189,7 +189,7 @@ describe('NodeParser - Output Extraction', () => {
description = nodeDescription;
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.outputs).toBeUndefined();
expect(result.outputNames).toBeUndefined();
@@ -229,7 +229,7 @@ describe('NodeParser - Output Extraction', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
// Should use latest version (3)
expect(result.outputs).toEqual([
@@ -259,7 +259,7 @@ describe('NodeParser - Output Extraction', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.outputs).toEqual(baseOutputs);
});
@@ -279,7 +279,7 @@ describe('NodeParser - Output Extraction', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.outputs).toEqual(ifOutputs);
expect(result.outputNames).toEqual(['true', 'false']);
@@ -300,7 +300,7 @@ describe('NodeParser - Output Extraction', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.outputs).toEqual(splitInBatchesOutputs);
expect(result.outputNames).toEqual(['done', 'loop']);
@@ -331,7 +331,7 @@ describe('NodeParser - Output Extraction', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.outputs).toEqual(switchOutputs);
expect(result.outputNames).toEqual(['0', '1', '2', 'fallback']);
@@ -347,7 +347,7 @@ describe('NodeParser - Output Extraction', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.outputs).toEqual([]);
expect(result.outputNames).toEqual([]);
@@ -369,7 +369,7 @@ describe('NodeParser - Output Extraction', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.outputs).toEqual(outputs);
expect(result.outputNames).toEqual(outputNames);
@@ -405,7 +405,7 @@ describe('NodeParser - Output Extraction', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.outputs).toHaveLength(2);
expect(result.outputs).toBeDefined();
@@ -442,7 +442,7 @@ describe('NodeParser - Output Extraction', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.outputs).toHaveLength(2);
expect(result.outputs).toBeDefined();
@@ -464,7 +464,7 @@ describe('NodeParser - Output Extraction', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.outputs).toBeUndefined();
expect(result.outputNames).toBeUndefined();

View File

@@ -47,7 +47,7 @@ describe('NodeParser', () => {
mockPropertyExtractor.extractProperties.mockReturnValue(nodeDefinition.properties);
mockPropertyExtractor.extractCredentials.mockReturnValue(nodeDefinition.credentials);
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result).toMatchObject({
style: 'programmatic',
@@ -70,7 +70,7 @@ describe('NodeParser', () => {
const nodeDefinition = declarativeNodeFactory.build();
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.style).toBe('declarative');
expect(result.nodeType).toBe(`nodes-base.${nodeDefinition.name}`);
@@ -82,7 +82,7 @@ describe('NodeParser', () => {
});
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.nodeType).toBe('nodes-base.slack');
});
@@ -91,7 +91,7 @@ describe('NodeParser', () => {
const nodeDefinition = triggerNodeFactory.build();
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.isTrigger).toBe(true);
});
@@ -100,7 +100,7 @@ describe('NodeParser', () => {
const nodeDefinition = webhookNodeFactory.build();
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.isWebhook).toBe(true);
});
@@ -111,7 +111,7 @@ describe('NodeParser', () => {
mockPropertyExtractor.detectAIToolCapability.mockReturnValue(true);
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.isAITool).toBe(true);
});
@@ -137,7 +137,7 @@ describe('NodeParser', () => {
propertyFactory.build()
]);
const result = parser.parse(VersionedNodeClass, 'n8n-nodes-base');
const result = parser.parse(VersionedNodeClass as any, 'n8n-nodes-base');
expect(result.isVersioned).toBe(true);
expect(result.version).toBe('2');
@@ -151,7 +151,7 @@ describe('NodeParser', () => {
baseDescription = versionedDef.baseDescription;
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.isVersioned).toBe(true);
expect(result.version).toBe('2');
@@ -163,7 +163,7 @@ describe('NodeParser', () => {
});
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.isVersioned).toBe(true);
expect(result.version).toBe('2'); // Should return max version
@@ -173,7 +173,7 @@ describe('NodeParser', () => {
const nodeDefinition = malformedNodeFactory.build();
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
expect(() => parser.parse(NodeClass, 'n8n-nodes-base')).toThrow('Node is missing name property');
expect(() => parser.parse(NodeClass as any, 'n8n-nodes-base')).toThrow('Node is missing name property');
});
it('should use static description when instantiation fails', () => {
@@ -184,7 +184,7 @@ describe('NodeParser', () => {
}
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.displayName).toBe(NodeClass.description.displayName);
});
@@ -205,7 +205,7 @@ describe('NodeParser', () => {
} as any);
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.category).toBe(expected);
});
@@ -217,7 +217,7 @@ describe('NodeParser', () => {
});
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.isTrigger).toBe(true);
});
@@ -228,7 +228,7 @@ describe('NodeParser', () => {
});
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.isTrigger).toBe(true);
});
@@ -239,7 +239,7 @@ describe('NodeParser', () => {
});
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.isTrigger).toBe(true);
});
@@ -250,7 +250,7 @@ describe('NodeParser', () => {
});
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.isWebhook).toBe(true);
});
@@ -262,8 +262,8 @@ describe('NodeParser', () => {
};
mockPropertyExtractor.extractProperties.mockReturnValue(nodeDefinition.properties);
const result = parser.parse(nodeInstance, 'n8n-nodes-base');
const result = parser.parse(nodeInstance as any, 'n8n-nodes-base');
expect(result.displayName).toBe(nodeDefinition.displayName);
});
@@ -279,7 +279,7 @@ describe('NodeParser', () => {
];
testCases.forEach(({ packageName, expectedPrefix }) => {
const result = parser.parse(NodeClass, packageName);
const result = parser.parse(NodeClass as any, packageName);
expect(result.nodeType).toBe(`${expectedPrefix}.${nodeDefinition.name}`);
});
});
@@ -296,7 +296,7 @@ describe('NodeParser', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.version).toBe('2.2');
});
@@ -310,7 +310,7 @@ describe('NodeParser', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.version).toBe('3');
});
@@ -325,7 +325,7 @@ describe('NodeParser', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.version).toBe('0');
});
@@ -339,7 +339,7 @@ describe('NodeParser', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.version).toBe('1'); // Should fallback to default
});
@@ -354,7 +354,7 @@ describe('NodeParser', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.version).toBe('3');
});
@@ -372,7 +372,7 @@ describe('NodeParser', () => {
}
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.version).toBe('4');
});
@@ -383,7 +383,7 @@ describe('NodeParser', () => {
});
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.version).toBe('2');
});
@@ -394,7 +394,7 @@ describe('NodeParser', () => {
});
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.version).toBe('1.5');
});
@@ -404,7 +404,7 @@ describe('NodeParser', () => {
delete (nodeDefinition as any).version;
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.version).toBe('1');
});
@@ -417,7 +417,7 @@ describe('NodeParser', () => {
nodeVersions = { 1: {}, 2: {} };
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.isVersioned).toBe(true);
});
@@ -431,7 +431,7 @@ describe('NodeParser', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.isVersioned).toBe(true);
});
@@ -445,7 +445,7 @@ describe('NodeParser', () => {
};
};
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.isVersioned).toBe(true);
});
@@ -456,7 +456,7 @@ describe('NodeParser', () => {
});
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.isVersioned).toBe(false);
});
@@ -468,7 +468,7 @@ describe('NodeParser', () => {
description = null;
};
expect(() => parser.parse(NodeClass, 'n8n-nodes-base')).toThrow();
expect(() => parser.parse(NodeClass as any, 'n8n-nodes-base')).toThrow();
});
it('should handle empty routing object for declarative nodes', () => {
@@ -477,7 +477,7 @@ describe('NodeParser', () => {
});
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.style).toBe('declarative');
});
@@ -503,7 +503,7 @@ describe('NodeParser', () => {
value: 'VersionedNodeType'
});
const result = parser.parse(NodeClass, 'n8n-nodes-base');
const result = parser.parse(NodeClass as any, 'n8n-nodes-base');
expect(result.isVersioned).toBe(true);
expect(result.version).toBe('3');

View File

@@ -30,7 +30,7 @@ describe('PropertyExtractor', () => {
const nodeDefinition = programmaticNodeFactory.build();
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const properties = extractor.extractProperties(NodeClass);
const properties = extractor.extractProperties(NodeClass as any);
expect(properties).toHaveLength(nodeDefinition.properties.length);
expect(properties).toEqual(expect.arrayContaining(
@@ -50,7 +50,7 @@ describe('PropertyExtractor', () => {
baseDescription = versionedDef.baseDescription;
};
const properties = extractor.extractProperties(NodeClass);
const properties = extractor.extractProperties(NodeClass as any);
// Should get properties from version 2 (latest)
expect(properties).toHaveLength(versionedDef.nodeVersions![2].description.properties.length);
@@ -78,7 +78,7 @@ describe('PropertyExtractor', () => {
}
};
const properties = extractor.extractProperties(NodeClass);
const properties = extractor.extractProperties(NodeClass as any);
expect(properties).toHaveLength(2);
expect(properties[0].name).toBe('v2prop1');
@@ -108,7 +108,7 @@ describe('PropertyExtractor', () => {
}
});
const properties = extractor.extractProperties(NodeClass);
const properties = extractor.extractProperties(NodeClass as any);
expect(properties[0]).toEqual({
displayName: 'Field 1',
@@ -135,7 +135,7 @@ describe('PropertyExtractor', () => {
}
});
const properties = extractor.extractProperties(NodeClass);
const properties = extractor.extractProperties(NodeClass as any);
expect(properties).toEqual([]);
});
@@ -151,7 +151,7 @@ describe('PropertyExtractor', () => {
}
};
const properties = extractor.extractProperties(NodeClass);
const properties = extractor.extractProperties(NodeClass as any);
expect(properties).toHaveLength(1); // Should get static description property
});
@@ -165,7 +165,7 @@ describe('PropertyExtractor', () => {
};
};
const properties = extractor.extractProperties(NodeClass);
const properties = extractor.extractProperties(NodeClass as any);
expect(properties).toHaveLength(1);
expect(properties[0].name).toBe('baseProp');
@@ -180,7 +180,7 @@ describe('PropertyExtractor', () => {
}
});
const properties = extractor.extractProperties(NodeClass);
const properties = extractor.extractProperties(NodeClass as any);
expect(properties).toHaveLength(1);
expect(properties[0].type).toBe('collection');
@@ -193,9 +193,9 @@ describe('PropertyExtractor', () => {
properties: [propertyFactory.build()]
}
};
const properties = extractor.extractProperties(nodeInstance);
const properties = extractor.extractProperties(nodeInstance as any);
expect(properties).toHaveLength(1);
});
});
@@ -205,7 +205,7 @@ describe('PropertyExtractor', () => {
const nodeDefinition = declarativeNodeFactory.build();
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const operations = extractor.extractOperations(NodeClass);
const operations = extractor.extractOperations(NodeClass as any);
// Declarative node has 2 resources with 2 operations each = 4 total
expect(operations.length).toBe(4);
@@ -235,7 +235,7 @@ describe('PropertyExtractor', () => {
}
});
const operations = extractor.extractOperations(NodeClass);
const operations = extractor.extractOperations(NodeClass as any);
expect(operations.length).toBe(operationProp.options!.length);
operations.forEach((op, idx) => {
@@ -261,7 +261,7 @@ describe('PropertyExtractor', () => {
}
});
const operations = extractor.extractOperations(NodeClass);
const operations = extractor.extractOperations(NodeClass as any);
// routing.operations is not currently extracted by the property extractor
// It only extracts from routing.request structure
@@ -292,7 +292,7 @@ describe('PropertyExtractor', () => {
}
});
const operations = extractor.extractOperations(NodeClass);
const operations = extractor.extractOperations(NodeClass as any);
// PropertyExtractor only extracts operations, not resources
// It should find the operation property and extract its options
@@ -317,7 +317,7 @@ describe('PropertyExtractor', () => {
}
});
const operations = extractor.extractOperations(NodeClass);
const operations = extractor.extractOperations(NodeClass as any);
expect(operations).toEqual([]);
});
@@ -353,7 +353,7 @@ describe('PropertyExtractor', () => {
};
};
const operations = extractor.extractOperations(NodeClass);
const operations = extractor.extractOperations(NodeClass as any);
expect(operations).toHaveLength(1);
expect(operations[0]).toMatchObject({
@@ -382,7 +382,7 @@ describe('PropertyExtractor', () => {
}
});
const operations = extractor.extractOperations(NodeClass);
const operations = extractor.extractOperations(NodeClass as any);
expect(operations).toHaveLength(2);
expect(operations[0].operation).toBe('send');
@@ -398,7 +398,7 @@ describe('PropertyExtractor', () => {
}
});
const isAITool = extractor.detectAIToolCapability(NodeClass);
const isAITool = extractor.detectAIToolCapability(NodeClass as any);
expect(isAITool).toBe(true);
});
@@ -414,7 +414,7 @@ describe('PropertyExtractor', () => {
}
});
const isAITool = extractor.detectAIToolCapability(NodeClass);
const isAITool = extractor.detectAIToolCapability(NodeClass as any);
expect(isAITool).toBe(true);
});
@@ -431,7 +431,7 @@ describe('PropertyExtractor', () => {
}
};
const isAITool = extractor.detectAIToolCapability(NodeClass);
const isAITool = extractor.detectAIToolCapability(NodeClass as any);
expect(isAITool).toBe(true);
});
@@ -444,7 +444,7 @@ describe('PropertyExtractor', () => {
description: { name }
});
const isAITool = extractor.detectAIToolCapability(NodeClass);
const isAITool = extractor.detectAIToolCapability(NodeClass as any);
expect(isAITool).toBe(true);
});
@@ -458,7 +458,7 @@ describe('PropertyExtractor', () => {
}
});
const isAITool = extractor.detectAIToolCapability(NodeClass);
const isAITool = extractor.detectAIToolCapability(NodeClass as any);
expect(isAITool).toBe(false);
});
@@ -466,7 +466,7 @@ describe('PropertyExtractor', () => {
it('should return false when node has no description', () => {
const NodeClass = class {};
const isAITool = extractor.detectAIToolCapability(NodeClass);
const isAITool = extractor.detectAIToolCapability(NodeClass as any);
expect(isAITool).toBe(false);
});
@@ -486,7 +486,7 @@ describe('PropertyExtractor', () => {
}
});
const extracted = extractor.extractCredentials(NodeClass);
const extracted = extractor.extractCredentials(NodeClass as any);
expect(extracted).toEqual(credentials);
});
@@ -510,7 +510,7 @@ describe('PropertyExtractor', () => {
};
};
const credentials = extractor.extractCredentials(NodeClass);
const credentials = extractor.extractCredentials(NodeClass as any);
expect(credentials).toHaveLength(2);
expect(credentials[0].name).toBe('oauth2');
@@ -525,7 +525,7 @@ describe('PropertyExtractor', () => {
}
});
const credentials = extractor.extractCredentials(NodeClass);
const credentials = extractor.extractCredentials(NodeClass as any);
expect(credentials).toEqual([]);
});
@@ -537,7 +537,7 @@ describe('PropertyExtractor', () => {
};
};
const credentials = extractor.extractCredentials(NodeClass);
const credentials = extractor.extractCredentials(NodeClass as any);
expect(credentials).toHaveLength(1);
expect(credentials[0].name).toBe('token');
@@ -554,7 +554,7 @@ describe('PropertyExtractor', () => {
}
};
const credentials = extractor.extractCredentials(NodeClass);
const credentials = extractor.extractCredentials(NodeClass as any);
expect(credentials).toHaveLength(1);
expect(credentials[0].name).toBe('jwt');
@@ -567,7 +567,7 @@ describe('PropertyExtractor', () => {
}
};
const credentials = extractor.extractCredentials(NodeClass);
const credentials = extractor.extractCredentials(NodeClass as any);
expect(credentials).toEqual([]);
});
@@ -605,7 +605,7 @@ describe('PropertyExtractor', () => {
}
});
const properties = extractor.extractProperties(NodeClass);
const properties = extractor.extractProperties(NodeClass as any);
expect(properties).toHaveLength(1);
expect(properties[0].name).toBe('deepOptions');
@@ -627,7 +627,7 @@ describe('PropertyExtractor', () => {
};
// Should not throw or hang
const properties = extractor.extractProperties(NodeClass);
const properties = extractor.extractProperties(NodeClass as any);
expect(properties).toBeDefined();
});
@@ -652,7 +652,7 @@ describe('PropertyExtractor', () => {
}
});
const operations = extractor.extractOperations(NodeClass);
const operations = extractor.extractOperations(NodeClass as any);
// Should extract from all sources
expect(operations.length).toBeGreaterThan(1);

View File

@@ -28,7 +28,7 @@ describe('SimpleParser', () => {
const nodeDefinition = programmaticNodeFactory.build();
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result).toMatchObject({
style: 'programmatic',
@@ -58,7 +58,7 @@ describe('SimpleParser', () => {
} as any;
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.style).toBe('declarative');
expect(result.operations.length).toBeGreaterThan(0);
@@ -68,7 +68,7 @@ describe('SimpleParser', () => {
const nodeDefinition = triggerNodeFactory.build();
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.isTrigger).toBe(true);
});
@@ -77,7 +77,7 @@ describe('SimpleParser', () => {
const nodeDefinition = webhookNodeFactory.build();
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.isWebhook).toBe(true);
});
@@ -92,7 +92,7 @@ describe('SimpleParser', () => {
} as any;
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.isAITool).toBe(true);
});
@@ -112,7 +112,7 @@ describe('SimpleParser', () => {
}
};
const result = parser.parse(VersionedNodeClass);
const result = parser.parse(VersionedNodeClass as any);
expect(result.isVersioned).toBe(true);
expect(result.nodeType).toBe(versionedDef.baseDescription!.name);
@@ -147,7 +147,7 @@ describe('SimpleParser', () => {
}
};
const result = parser.parse(VersionedNodeClass);
const result = parser.parse(VersionedNodeClass as any);
// Should merge baseDescription with version description
expect(result.nodeType).toBe('mergedNode'); // From base
@@ -159,7 +159,7 @@ describe('SimpleParser', () => {
const nodeDefinition = malformedNodeFactory.build();
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
expect(() => parser.parse(NodeClass)).toThrow('Node is missing name property');
expect(() => parser.parse(NodeClass as any)).toThrow('Node is missing name property');
});
it('should handle nodes that fail to instantiate', () => {
@@ -169,7 +169,7 @@ describe('SimpleParser', () => {
}
};
expect(() => parser.parse(NodeClass)).toThrow('Node is missing name property');
expect(() => parser.parse(NodeClass as any)).toThrow('Node is missing name property');
});
it('should handle static description property', () => {
@@ -180,7 +180,7 @@ describe('SimpleParser', () => {
// Since it can't instantiate and has no static description accessible,
// it should throw for missing name
expect(() => parser.parse(NodeClass)).toThrow();
expect(() => parser.parse(NodeClass as any)).toThrow();
});
it('should handle instance-based nodes', () => {
@@ -189,7 +189,7 @@ describe('SimpleParser', () => {
description: nodeDefinition
};
const result = parser.parse(nodeInstance);
const result = parser.parse(nodeInstance as any);
expect(result.displayName).toBe(nodeDefinition.displayName);
});
@@ -199,7 +199,7 @@ describe('SimpleParser', () => {
delete (nodeDefinition as any).displayName;
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.displayName).toBe(nodeDefinition.name);
});
@@ -233,7 +233,7 @@ describe('SimpleParser', () => {
};
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.category).toBe(expected);
});
@@ -247,7 +247,7 @@ describe('SimpleParser', () => {
});
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.isTrigger).toBe(true);
});
@@ -258,7 +258,7 @@ describe('SimpleParser', () => {
});
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.isTrigger).toBe(true);
});
@@ -269,7 +269,7 @@ describe('SimpleParser', () => {
});
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.isTrigger).toBe(true);
});
@@ -280,7 +280,7 @@ describe('SimpleParser', () => {
});
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.isTrigger).toBe(true);
});
@@ -291,7 +291,7 @@ describe('SimpleParser', () => {
});
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.isTrigger).toBe(true);
});
@@ -309,7 +309,7 @@ describe('SimpleParser', () => {
};
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
// Should have resource operations
const resourceOps = result.operations.filter(op => op.resource);
@@ -335,7 +335,7 @@ describe('SimpleParser', () => {
}
});
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.operations).toHaveLength(4);
expect(result.operations).toEqual(expect.arrayContaining([
@@ -355,7 +355,7 @@ describe('SimpleParser', () => {
}
});
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
const resourceOps = result.operations.filter(op => op.type === 'resource');
expect(resourceOps).toHaveLength(resourceProp.options!.length);
@@ -377,7 +377,7 @@ describe('SimpleParser', () => {
}
});
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
const operationOps = result.operations.filter(op => op.type === 'operation');
expect(operationOps).toHaveLength(operationProp.options!.length);
@@ -407,7 +407,7 @@ describe('SimpleParser', () => {
}
});
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
const operationOps = result.operations.filter(op => op.type === 'operation');
expect(operationOps[0].resources).toEqual(['user', 'post', 'comment']);
@@ -434,7 +434,7 @@ describe('SimpleParser', () => {
}
});
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
const operationOps = result.operations.filter(op => op.type === 'operation');
expect(operationOps[0].resources).toEqual(['user']);
@@ -442,10 +442,38 @@ describe('SimpleParser', () => {
});
describe('version extraction', () => {
it('should extract version from baseDescription.defaultVersion', () => {
// Simple parser needs a proper versioned node structure
it('should prioritize currentVersion over description.defaultVersion', () => {
const NodeClass = class {
baseDescription = {
currentVersion = 2.2; // Should be returned
description = {
name: 'test',
displayName: 'Test',
defaultVersion: 3 // Should be ignored when currentVersion exists
};
};
const result = parser.parse(NodeClass as any);
expect(result.version).toBe('2.2');
});
it('should extract version from description.defaultVersion', () => {
const NodeClass = class {
description = {
name: 'test',
displayName: 'Test',
defaultVersion: 3
};
};
const result = parser.parse(NodeClass as any);
expect(result.version).toBe('3');
});
it('should NOT extract version from non-existent baseDescription (legacy bug)', () => {
// This test verifies the bug fix from v2.17.4
// baseDescription.defaultVersion doesn't exist on VersionedNodeType instances
const NodeClass = class {
baseDescription = { // This property doesn't exist on VersionedNodeType!
name: 'test',
displayName: 'Test',
defaultVersion: 3
@@ -458,10 +486,11 @@ describe('SimpleParser', () => {
});
}
};
const result = parser.parse(NodeClass);
expect(result.version).toBe('3');
const result = parser.parse(NodeClass as any);
// Should fallback to default version '1' since baseDescription.defaultVersion doesn't exist
expect(result.version).toBe('1');
});
it('should extract version from description.version', () => {
@@ -473,7 +502,7 @@ describe('SimpleParser', () => {
};
};
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.version).toBe('2');
});
@@ -485,7 +514,7 @@ describe('SimpleParser', () => {
}
});
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.version).toBe('1');
});
@@ -509,7 +538,7 @@ describe('SimpleParser', () => {
}
};
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.isVersioned).toBe(true);
});
@@ -522,7 +551,7 @@ describe('SimpleParser', () => {
}
});
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.isVersioned).toBe(true);
});
@@ -535,7 +564,7 @@ describe('SimpleParser', () => {
}
});
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.isVersioned).toBe(true);
});
@@ -548,7 +577,7 @@ describe('SimpleParser', () => {
};
};
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.isVersioned).toBe(true);
});
@@ -563,7 +592,7 @@ describe('SimpleParser', () => {
}
});
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.style).toBe('declarative');
expect(result.operations).toEqual([]);
@@ -576,7 +605,7 @@ describe('SimpleParser', () => {
}
});
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.properties).toEqual([]);
});
@@ -586,7 +615,7 @@ describe('SimpleParser', () => {
delete (nodeDefinition as any).credentials;
const NodeClass = nodeClassFactory.build({ description: nodeDefinition });
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.credentials).toEqual([]);
});
@@ -600,7 +629,7 @@ describe('SimpleParser', () => {
};
};
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.nodeType).toBe('baseNode');
expect(result.displayName).toBe('Base Node');
@@ -624,7 +653,7 @@ describe('SimpleParser', () => {
}
});
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
expect(result.operations).toEqual([]);
});
@@ -649,7 +678,7 @@ describe('SimpleParser', () => {
}
});
const result = parser.parse(NodeClass);
const result = parser.parse(NodeClass as any);
// Should handle missing names gracefully
expect(result.operations).toHaveLength(2);