From 974b7b3c1be7e60f1a2157ce9af4556055252c4c Mon Sep 17 00:00:00 2001 From: Aadil-Hasun Date: Mon, 19 Jan 2026 02:06:56 +0530 Subject: [PATCH 1/4] fix(components): add missing regex handler to Condition node The Condition agentflow node's Regex operation was not functional because compareOperationFunctions was missing the regex handler. Changes: - Added regex handler with unescapeRegexPattern helper function - Handles Flowise's escaping of regex metacharacters - Gracefully returns false for invalid regex patterns - Added 22 unit tests Fixes #5650 --- .../agentflow/Condition/Condition.test.ts | 218 ++++++++++++++++++ .../nodes/agentflow/Condition/Condition.ts | 21 ++ 2 files changed, 239 insertions(+) create mode 100644 packages/components/nodes/agentflow/Condition/Condition.test.ts diff --git a/packages/components/nodes/agentflow/Condition/Condition.test.ts b/packages/components/nodes/agentflow/Condition/Condition.test.ts new file mode 100644 index 00000000000..bec5f62412c --- /dev/null +++ b/packages/components/nodes/agentflow/Condition/Condition.test.ts @@ -0,0 +1,218 @@ +const { nodeClass: Condition_Agentflow } = require('./Condition') +import { INodeData } from '../../../src/Interface' + +// Helper function to create a valid INodeData object for Condition node +function createConditionNodeData(id: string, conditions: any[]): INodeData { + return { + id: id, + label: 'Condition', + name: 'conditionAgentflow', + type: 'Condition', + icon: 'condition.svg', + version: 1.0, + category: 'Agent Flows', + baseClasses: ['Condition'], + inputs: { + conditions: conditions + } + } +} + +describe('Condition Agentflow - Regex Operation', () => { + let nodeClass: any + + beforeEach(() => { + nodeClass = new Condition_Agentflow() + }) + + describe('Valid regex patterns', () => { + it('should match when regex pattern matches value1', async () => { + const conditions = [ + { type: 'string', value1: 'hello world', operation: 'regex', value2: 'hello' } + ] + const nodeData = createConditionNodeData('test-regex-1', conditions) + const result = await nodeClass.run(nodeData, '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + + it('should not match when regex pattern does not match value1', async () => { + const conditions = [ + { type: 'string', value1: 'hello world', operation: 'regex', value2: '^world' } + ] + const nodeData = createConditionNodeData('test-regex-2', conditions) + const result = await nodeClass.run(nodeData, '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBeUndefined() + expect(result.output.conditions[1].isFulfilled).toBe(true) + }) + + it('should match digits with [0-9]+ pattern', async () => { + const conditions = [ + { type: 'string', value1: 'test123abc', operation: 'regex', value2: '[0-9]+' } + ] + const result = await nodeClass.run(createConditionNodeData('test-3', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + + it('should match email pattern', async () => { + const conditions = [ + { type: 'string', value1: 'user@example.com', operation: 'regex', value2: '^[a-zA-Z0-9.]+@[a-zA-Z0-9.]+$' } + ] + const result = await nodeClass.run(createConditionNodeData('test-4', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + }) + + describe('Invalid regex patterns', () => { + it('should return false (not crash) for invalid regex pattern', async () => { + const conditions = [ + { type: 'string', value1: 'test', operation: 'regex', value2: '[invalid(' } + ] + const result = await nodeClass.run(createConditionNodeData('test-invalid', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBeUndefined() + expect(result.output.conditions[1].isFulfilled).toBe(true) + }) + }) + + describe('Edge cases', () => { + it('should handle empty value1', async () => { + const conditions = [ + { type: 'string', value1: '', operation: 'regex', value2: '.*' } + ] + const result = await nodeClass.run(createConditionNodeData('test-empty', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + + it('should handle null value1', async () => { + const conditions = [ + { type: 'string', value1: null, operation: 'regex', value2: 'test' } + ] + const result = await nodeClass.run(createConditionNodeData('test-null', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBeUndefined() + }) + + it('should be case-sensitive by default', async () => { + const conditions = [ + { type: 'string', value1: 'Hello', operation: 'regex', value2: 'hello' } + ] + const result = await nodeClass.run(createConditionNodeData('test-case', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBeUndefined() + }) + }) + + describe('Flowise input escaping', () => { + // Flowise escapes special characters: \ → \\, [ → \[, ] → \] + // Our regex handler unescapes these + + it('should unescape brackets in pattern', async () => { + // User typed [0-9]+, Flowise stored as \[0-9\]+ + const conditions = [ + { type: 'string', value1: 'test123abc', operation: 'regex', value2: '\\[0-9\\]+' } + ] + const result = await nodeClass.run(createConditionNodeData('test-brackets', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + + it('should unescape double-backslash pattern', async () => { + // User typed \d+, Flowise stored as \\d+ (2 backslashes) + // In JS string literal: '\\\\d+' represents 2 actual backslashes + d+ + const conditions = [ + { type: 'string', value1: 'test123abc', operation: 'regex', value2: '\\\\d+' } + ] + const result = await nodeClass.run(createConditionNodeData('test-backslash', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + }) + + describe('Complex regex patterns - all metacharacters', () => { + // Proving that Flowise does NOT escape: ^, $, ., *, +, ?, (, ), {, }, | + + it('should handle ^ (start anchor)', async () => { + const conditions = [{ type: 'string', value1: 'hello world', operation: 'regex', value2: '^hello' }] + const result = await new Condition_Agentflow().run(createConditionNodeData('test-caret', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + + it('should handle $ (end anchor)', async () => { + const conditions = [{ type: 'string', value1: 'hello world', operation: 'regex', value2: 'world$' }] + const result = await new Condition_Agentflow().run(createConditionNodeData('test-dollar', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + + it('should handle . (any character)', async () => { + const conditions = [{ type: 'string', value1: 'cat', operation: 'regex', value2: 'c.t' }] + const result = await new Condition_Agentflow().run(createConditionNodeData('test-dot', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + + it('should handle * (zero or more)', async () => { + const conditions = [{ type: 'string', value1: 'goooal', operation: 'regex', value2: 'go*al' }] + const result = await new Condition_Agentflow().run(createConditionNodeData('test-star', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + + it('should handle + (one or more)', async () => { + const conditions = [{ type: 'string', value1: 'goooal', operation: 'regex', value2: 'go+al' }] + const result = await new Condition_Agentflow().run(createConditionNodeData('test-plus', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + + it('should handle ? (zero or one)', async () => { + const conditions = [{ type: 'string', value1: 'color', operation: 'regex', value2: 'colou?r' }] + const result = await new Condition_Agentflow().run(createConditionNodeData('test-question', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + + it('should handle | (alternation)', async () => { + const conditions = [{ type: 'string', value1: 'cat', operation: 'regex', value2: 'cat|dog' }] + const result = await new Condition_Agentflow().run(createConditionNodeData('test-pipe', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + + it('should handle () (grouping)', async () => { + const conditions = [{ type: 'string', value1: 'abcabc', operation: 'regex', value2: '(abc)+' }] + const result = await new Condition_Agentflow().run(createConditionNodeData('test-parens', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + + it('should handle {} (quantifier)', async () => { + const conditions = [{ type: 'string', value1: 'aaa', operation: 'regex', value2: 'a{3}' }] + const result = await new Condition_Agentflow().run(createConditionNodeData('test-braces', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + + it('should handle complex email pattern', async () => { + const conditions = [{ + type: 'string', + value1: 'user@example.com', + operation: 'regex', + // ^[a-z]+@[a-z]+\.(com|org)$ - the \. needs escaping in JS + value2: '^[a-z]+@[a-z]+\\.(com|org)$' + }] + const result = await new Condition_Agentflow().run(createConditionNodeData('test-complex', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + + it('should handle URL pattern', async () => { + const conditions = [{ + type: 'string', + value1: 'https://example.com/path?query=1', + operation: 'regex', + value2: '^https?://[a-z.]+/.*$' + }] + const result = await new Condition_Agentflow().run(createConditionNodeData('test-url', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + + it('should handle UUID pattern', async () => { + const conditions = [{ + type: 'string', + value1: '550e8400-e29b-41d4-a716-446655440000', + operation: 'regex', + value2: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' + }] + const result = await new Condition_Agentflow().run(createConditionNodeData('test-uuid', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) + }) +}) + diff --git a/packages/components/nodes/agentflow/Condition/Condition.ts b/packages/components/nodes/agentflow/Condition/Condition.ts index 7ae1be06291..31badd22414 100644 --- a/packages/components/nodes/agentflow/Condition/Condition.ts +++ b/packages/components/nodes/agentflow/Condition/Condition.ts @@ -275,6 +275,27 @@ class Condition_Agentflow implements INode { smaller: (value1: CommonType, value2: CommonType) => (Number(value1) || 0) < (Number(value2) || 0), smallerEqual: (value1: CommonType, value2: CommonType) => (Number(value1) || 0) <= (Number(value2) || 0), startsWith: (value1: CommonType, value2: CommonType) => (value1 as string).startsWith(value2 as string), + regex: (value1: CommonType, value2: CommonType) => { + /** + * Unescapes a regex pattern that was escaped by Flowise input handling. + * Flowise escapes regex metacharacters in inputs by prefixing with backslash. + * We reverse this to get the user's intended regex pattern. + */ + const unescapeRegexPattern = (escaped: string): string => { + return escaped + .replace(/\\\\/g, '\0') // Preserve intentional backslashes (\\) + .replace(/\\([[\].*+?^${}()|])/g, '$1') // Unescape all regex metacharacters + .replace(/\0/g, '\\') // Restore preserved backslashes + } + + try { + const pattern = unescapeRegexPattern((value2 || '').toString()) + return new RegExp(pattern).test((value1 || '').toString()) + } catch { + // Invalid regex pattern - fail gracefully + return false + } + }, isEmpty: (value1: CommonType) => [undefined, null, ''].includes(value1 as string), notEmpty: (value1: CommonType) => ![undefined, null, ''].includes(value1 as string) } From 15d0f256fe20920cd7776fca2489d52d6cb05b1b Mon Sep 17 00:00:00 2001 From: Aadil-Hasun Date: Mon, 19 Jan 2026 02:28:44 +0530 Subject: [PATCH 2/4] refactor: address code review feedback - Move unescapeRegexPattern to module level for performance - Restrict unescaping to only [, ], * (verified by UI testing) - Use nodeClass from beforeEach in all tests for consistency - Add else condition assertions for non-matching tests - Add test for escaped asterisk --- .../agentflow/Condition/Condition.test.ts | 45 ++++++++++--------- .../nodes/agentflow/Condition/Condition.ts | 25 +++++------ 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/packages/components/nodes/agentflow/Condition/Condition.test.ts b/packages/components/nodes/agentflow/Condition/Condition.test.ts index bec5f62412c..8cb8dee161c 100644 --- a/packages/components/nodes/agentflow/Condition/Condition.test.ts +++ b/packages/components/nodes/agentflow/Condition/Condition.test.ts @@ -88,6 +88,7 @@ describe('Condition Agentflow - Regex Operation', () => { ] const result = await nodeClass.run(createConditionNodeData('test-null', conditions), '', { agentflowRuntime: { state: {} } }) expect(result.output.conditions[0].isFulfilled).toBeUndefined() + expect(result.output.conditions[1].isFulfilled).toBe(true) }) it('should be case-sensitive by default', async () => { @@ -96,15 +97,12 @@ describe('Condition Agentflow - Regex Operation', () => { ] const result = await nodeClass.run(createConditionNodeData('test-case', conditions), '', { agentflowRuntime: { state: {} } }) expect(result.output.conditions[0].isFulfilled).toBeUndefined() + expect(result.output.conditions[1].isFulfilled).toBe(true) }) }) describe('Flowise input escaping', () => { - // Flowise escapes special characters: \ → \\, [ → \[, ] → \] - // Our regex handler unescapes these - it('should unescape brackets in pattern', async () => { - // User typed [0-9]+, Flowise stored as \[0-9\]+ const conditions = [ { type: 'string', value1: 'test123abc', operation: 'regex', value2: '\\[0-9\\]+' } ] @@ -113,70 +111,75 @@ describe('Condition Agentflow - Regex Operation', () => { }) it('should unescape double-backslash pattern', async () => { - // User typed \d+, Flowise stored as \\d+ (2 backslashes) - // In JS string literal: '\\\\d+' represents 2 actual backslashes + d+ const conditions = [ { type: 'string', value1: 'test123abc', operation: 'regex', value2: '\\\\d+' } ] const result = await nodeClass.run(createConditionNodeData('test-backslash', conditions), '', { agentflowRuntime: { state: {} } }) expect(result.output.conditions[0].isFulfilled).toBe(true) }) + + it('should unescape escaped asterisk', async () => { + // User typed go*al, Flowise stored as go\*al + const conditions = [ + { type: 'string', value1: 'goooal', operation: 'regex', value2: 'go\\*al' } + ] + const result = await nodeClass.run(createConditionNodeData('test-escaped-asterisk', conditions), '', { agentflowRuntime: { state: {} } }) + expect(result.output.conditions[0].isFulfilled).toBe(true) + }) }) describe('Complex regex patterns - all metacharacters', () => { - // Proving that Flowise does NOT escape: ^, $, ., *, +, ?, (, ), {, }, | - it('should handle ^ (start anchor)', async () => { const conditions = [{ type: 'string', value1: 'hello world', operation: 'regex', value2: '^hello' }] - const result = await new Condition_Agentflow().run(createConditionNodeData('test-caret', conditions), '', { agentflowRuntime: { state: {} } }) + const result = await nodeClass.run(createConditionNodeData('test-caret', conditions), '', { agentflowRuntime: { state: {} } }) expect(result.output.conditions[0].isFulfilled).toBe(true) }) it('should handle $ (end anchor)', async () => { const conditions = [{ type: 'string', value1: 'hello world', operation: 'regex', value2: 'world$' }] - const result = await new Condition_Agentflow().run(createConditionNodeData('test-dollar', conditions), '', { agentflowRuntime: { state: {} } }) + const result = await nodeClass.run(createConditionNodeData('test-dollar', conditions), '', { agentflowRuntime: { state: {} } }) expect(result.output.conditions[0].isFulfilled).toBe(true) }) it('should handle . (any character)', async () => { const conditions = [{ type: 'string', value1: 'cat', operation: 'regex', value2: 'c.t' }] - const result = await new Condition_Agentflow().run(createConditionNodeData('test-dot', conditions), '', { agentflowRuntime: { state: {} } }) + const result = await nodeClass.run(createConditionNodeData('test-dot', conditions), '', { agentflowRuntime: { state: {} } }) expect(result.output.conditions[0].isFulfilled).toBe(true) }) it('should handle * (zero or more)', async () => { const conditions = [{ type: 'string', value1: 'goooal', operation: 'regex', value2: 'go*al' }] - const result = await new Condition_Agentflow().run(createConditionNodeData('test-star', conditions), '', { agentflowRuntime: { state: {} } }) + const result = await nodeClass.run(createConditionNodeData('test-star', conditions), '', { agentflowRuntime: { state: {} } }) expect(result.output.conditions[0].isFulfilled).toBe(true) }) it('should handle + (one or more)', async () => { const conditions = [{ type: 'string', value1: 'goooal', operation: 'regex', value2: 'go+al' }] - const result = await new Condition_Agentflow().run(createConditionNodeData('test-plus', conditions), '', { agentflowRuntime: { state: {} } }) + const result = await nodeClass.run(createConditionNodeData('test-plus', conditions), '', { agentflowRuntime: { state: {} } }) expect(result.output.conditions[0].isFulfilled).toBe(true) }) it('should handle ? (zero or one)', async () => { const conditions = [{ type: 'string', value1: 'color', operation: 'regex', value2: 'colou?r' }] - const result = await new Condition_Agentflow().run(createConditionNodeData('test-question', conditions), '', { agentflowRuntime: { state: {} } }) + const result = await nodeClass.run(createConditionNodeData('test-question', conditions), '', { agentflowRuntime: { state: {} } }) expect(result.output.conditions[0].isFulfilled).toBe(true) }) it('should handle | (alternation)', async () => { const conditions = [{ type: 'string', value1: 'cat', operation: 'regex', value2: 'cat|dog' }] - const result = await new Condition_Agentflow().run(createConditionNodeData('test-pipe', conditions), '', { agentflowRuntime: { state: {} } }) + const result = await nodeClass.run(createConditionNodeData('test-pipe', conditions), '', { agentflowRuntime: { state: {} } }) expect(result.output.conditions[0].isFulfilled).toBe(true) }) it('should handle () (grouping)', async () => { const conditions = [{ type: 'string', value1: 'abcabc', operation: 'regex', value2: '(abc)+' }] - const result = await new Condition_Agentflow().run(createConditionNodeData('test-parens', conditions), '', { agentflowRuntime: { state: {} } }) + const result = await nodeClass.run(createConditionNodeData('test-parens', conditions), '', { agentflowRuntime: { state: {} } }) expect(result.output.conditions[0].isFulfilled).toBe(true) }) it('should handle {} (quantifier)', async () => { const conditions = [{ type: 'string', value1: 'aaa', operation: 'regex', value2: 'a{3}' }] - const result = await new Condition_Agentflow().run(createConditionNodeData('test-braces', conditions), '', { agentflowRuntime: { state: {} } }) + const result = await nodeClass.run(createConditionNodeData('test-braces', conditions), '', { agentflowRuntime: { state: {} } }) expect(result.output.conditions[0].isFulfilled).toBe(true) }) @@ -185,10 +188,9 @@ describe('Condition Agentflow - Regex Operation', () => { type: 'string', value1: 'user@example.com', operation: 'regex', - // ^[a-z]+@[a-z]+\.(com|org)$ - the \. needs escaping in JS value2: '^[a-z]+@[a-z]+\\.(com|org)$' }] - const result = await new Condition_Agentflow().run(createConditionNodeData('test-complex', conditions), '', { agentflowRuntime: { state: {} } }) + const result = await nodeClass.run(createConditionNodeData('test-complex', conditions), '', { agentflowRuntime: { state: {} } }) expect(result.output.conditions[0].isFulfilled).toBe(true) }) @@ -199,7 +201,7 @@ describe('Condition Agentflow - Regex Operation', () => { operation: 'regex', value2: '^https?://[a-z.]+/.*$' }] - const result = await new Condition_Agentflow().run(createConditionNodeData('test-url', conditions), '', { agentflowRuntime: { state: {} } }) + const result = await nodeClass.run(createConditionNodeData('test-url', conditions), '', { agentflowRuntime: { state: {} } }) expect(result.output.conditions[0].isFulfilled).toBe(true) }) @@ -210,9 +212,8 @@ describe('Condition Agentflow - Regex Operation', () => { operation: 'regex', value2: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' }] - const result = await new Condition_Agentflow().run(createConditionNodeData('test-uuid', conditions), '', { agentflowRuntime: { state: {} } }) + const result = await nodeClass.run(createConditionNodeData('test-uuid', conditions), '', { agentflowRuntime: { state: {} } }) expect(result.output.conditions[0].isFulfilled).toBe(true) }) }) }) - diff --git a/packages/components/nodes/agentflow/Condition/Condition.ts b/packages/components/nodes/agentflow/Condition/Condition.ts index 31badd22414..f87816f8465 100644 --- a/packages/components/nodes/agentflow/Condition/Condition.ts +++ b/packages/components/nodes/agentflow/Condition/Condition.ts @@ -1,6 +1,18 @@ import { CommonType, ICommonObject, ICondition, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' import removeMarkdown from 'remove-markdown' +/** + * Unescapes a regex pattern that was escaped by Flowise input handling. + * Flowise escapes these characters: \ → \\, [ → \[, ] → \], * → \* + * We reverse this to get the user's intended regex pattern. + */ +const unescapeRegexPattern = (escaped: string): string => { + return escaped + .replace(/\\\\/g, '\0') // Preserve intentional backslashes + .replace(/\\([[\]*])/g, '$1') // Unescape only: [ ] * + .replace(/\0/g, '\\') // Restore preserved backslashes +} + class Condition_Agentflow implements INode { label: string name: string @@ -276,23 +288,10 @@ class Condition_Agentflow implements INode { smallerEqual: (value1: CommonType, value2: CommonType) => (Number(value1) || 0) <= (Number(value2) || 0), startsWith: (value1: CommonType, value2: CommonType) => (value1 as string).startsWith(value2 as string), regex: (value1: CommonType, value2: CommonType) => { - /** - * Unescapes a regex pattern that was escaped by Flowise input handling. - * Flowise escapes regex metacharacters in inputs by prefixing with backslash. - * We reverse this to get the user's intended regex pattern. - */ - const unescapeRegexPattern = (escaped: string): string => { - return escaped - .replace(/\\\\/g, '\0') // Preserve intentional backslashes (\\) - .replace(/\\([[\].*+?^${}()|])/g, '$1') // Unescape all regex metacharacters - .replace(/\0/g, '\\') // Restore preserved backslashes - } - try { const pattern = unescapeRegexPattern((value2 || '').toString()) return new RegExp(pattern).test((value1 || '').toString()) } catch { - // Invalid regex pattern - fail gracefully return false } }, From 4d6c8c1146e25e07c96c2e9bef2dae07cf13be23 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Fri, 27 Feb 2026 07:40:43 +0800 Subject: [PATCH 3/4] Delete packages/components/nodes/agentflow/Condition/Condition.test.ts --- .../agentflow/Condition/Condition.test.ts | 219 ------------------ 1 file changed, 219 deletions(-) delete mode 100644 packages/components/nodes/agentflow/Condition/Condition.test.ts diff --git a/packages/components/nodes/agentflow/Condition/Condition.test.ts b/packages/components/nodes/agentflow/Condition/Condition.test.ts deleted file mode 100644 index 8cb8dee161c..00000000000 --- a/packages/components/nodes/agentflow/Condition/Condition.test.ts +++ /dev/null @@ -1,219 +0,0 @@ -const { nodeClass: Condition_Agentflow } = require('./Condition') -import { INodeData } from '../../../src/Interface' - -// Helper function to create a valid INodeData object for Condition node -function createConditionNodeData(id: string, conditions: any[]): INodeData { - return { - id: id, - label: 'Condition', - name: 'conditionAgentflow', - type: 'Condition', - icon: 'condition.svg', - version: 1.0, - category: 'Agent Flows', - baseClasses: ['Condition'], - inputs: { - conditions: conditions - } - } -} - -describe('Condition Agentflow - Regex Operation', () => { - let nodeClass: any - - beforeEach(() => { - nodeClass = new Condition_Agentflow() - }) - - describe('Valid regex patterns', () => { - it('should match when regex pattern matches value1', async () => { - const conditions = [ - { type: 'string', value1: 'hello world', operation: 'regex', value2: 'hello' } - ] - const nodeData = createConditionNodeData('test-regex-1', conditions) - const result = await nodeClass.run(nodeData, '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - - it('should not match when regex pattern does not match value1', async () => { - const conditions = [ - { type: 'string', value1: 'hello world', operation: 'regex', value2: '^world' } - ] - const nodeData = createConditionNodeData('test-regex-2', conditions) - const result = await nodeClass.run(nodeData, '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBeUndefined() - expect(result.output.conditions[1].isFulfilled).toBe(true) - }) - - it('should match digits with [0-9]+ pattern', async () => { - const conditions = [ - { type: 'string', value1: 'test123abc', operation: 'regex', value2: '[0-9]+' } - ] - const result = await nodeClass.run(createConditionNodeData('test-3', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - - it('should match email pattern', async () => { - const conditions = [ - { type: 'string', value1: 'user@example.com', operation: 'regex', value2: '^[a-zA-Z0-9.]+@[a-zA-Z0-9.]+$' } - ] - const result = await nodeClass.run(createConditionNodeData('test-4', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - }) - - describe('Invalid regex patterns', () => { - it('should return false (not crash) for invalid regex pattern', async () => { - const conditions = [ - { type: 'string', value1: 'test', operation: 'regex', value2: '[invalid(' } - ] - const result = await nodeClass.run(createConditionNodeData('test-invalid', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBeUndefined() - expect(result.output.conditions[1].isFulfilled).toBe(true) - }) - }) - - describe('Edge cases', () => { - it('should handle empty value1', async () => { - const conditions = [ - { type: 'string', value1: '', operation: 'regex', value2: '.*' } - ] - const result = await nodeClass.run(createConditionNodeData('test-empty', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - - it('should handle null value1', async () => { - const conditions = [ - { type: 'string', value1: null, operation: 'regex', value2: 'test' } - ] - const result = await nodeClass.run(createConditionNodeData('test-null', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBeUndefined() - expect(result.output.conditions[1].isFulfilled).toBe(true) - }) - - it('should be case-sensitive by default', async () => { - const conditions = [ - { type: 'string', value1: 'Hello', operation: 'regex', value2: 'hello' } - ] - const result = await nodeClass.run(createConditionNodeData('test-case', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBeUndefined() - expect(result.output.conditions[1].isFulfilled).toBe(true) - }) - }) - - describe('Flowise input escaping', () => { - it('should unescape brackets in pattern', async () => { - const conditions = [ - { type: 'string', value1: 'test123abc', operation: 'regex', value2: '\\[0-9\\]+' } - ] - const result = await nodeClass.run(createConditionNodeData('test-brackets', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - - it('should unescape double-backslash pattern', async () => { - const conditions = [ - { type: 'string', value1: 'test123abc', operation: 'regex', value2: '\\\\d+' } - ] - const result = await nodeClass.run(createConditionNodeData('test-backslash', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - - it('should unescape escaped asterisk', async () => { - // User typed go*al, Flowise stored as go\*al - const conditions = [ - { type: 'string', value1: 'goooal', operation: 'regex', value2: 'go\\*al' } - ] - const result = await nodeClass.run(createConditionNodeData('test-escaped-asterisk', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - }) - - describe('Complex regex patterns - all metacharacters', () => { - it('should handle ^ (start anchor)', async () => { - const conditions = [{ type: 'string', value1: 'hello world', operation: 'regex', value2: '^hello' }] - const result = await nodeClass.run(createConditionNodeData('test-caret', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - - it('should handle $ (end anchor)', async () => { - const conditions = [{ type: 'string', value1: 'hello world', operation: 'regex', value2: 'world$' }] - const result = await nodeClass.run(createConditionNodeData('test-dollar', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - - it('should handle . (any character)', async () => { - const conditions = [{ type: 'string', value1: 'cat', operation: 'regex', value2: 'c.t' }] - const result = await nodeClass.run(createConditionNodeData('test-dot', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - - it('should handle * (zero or more)', async () => { - const conditions = [{ type: 'string', value1: 'goooal', operation: 'regex', value2: 'go*al' }] - const result = await nodeClass.run(createConditionNodeData('test-star', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - - it('should handle + (one or more)', async () => { - const conditions = [{ type: 'string', value1: 'goooal', operation: 'regex', value2: 'go+al' }] - const result = await nodeClass.run(createConditionNodeData('test-plus', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - - it('should handle ? (zero or one)', async () => { - const conditions = [{ type: 'string', value1: 'color', operation: 'regex', value2: 'colou?r' }] - const result = await nodeClass.run(createConditionNodeData('test-question', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - - it('should handle | (alternation)', async () => { - const conditions = [{ type: 'string', value1: 'cat', operation: 'regex', value2: 'cat|dog' }] - const result = await nodeClass.run(createConditionNodeData('test-pipe', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - - it('should handle () (grouping)', async () => { - const conditions = [{ type: 'string', value1: 'abcabc', operation: 'regex', value2: '(abc)+' }] - const result = await nodeClass.run(createConditionNodeData('test-parens', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - - it('should handle {} (quantifier)', async () => { - const conditions = [{ type: 'string', value1: 'aaa', operation: 'regex', value2: 'a{3}' }] - const result = await nodeClass.run(createConditionNodeData('test-braces', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - - it('should handle complex email pattern', async () => { - const conditions = [{ - type: 'string', - value1: 'user@example.com', - operation: 'regex', - value2: '^[a-z]+@[a-z]+\\.(com|org)$' - }] - const result = await nodeClass.run(createConditionNodeData('test-complex', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - - it('should handle URL pattern', async () => { - const conditions = [{ - type: 'string', - value1: 'https://example.com/path?query=1', - operation: 'regex', - value2: '^https?://[a-z.]+/.*$' - }] - const result = await nodeClass.run(createConditionNodeData('test-url', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - - it('should handle UUID pattern', async () => { - const conditions = [{ - type: 'string', - value1: '550e8400-e29b-41d4-a716-446655440000', - operation: 'regex', - value2: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' - }] - const result = await nodeClass.run(createConditionNodeData('test-uuid', conditions), '', { agentflowRuntime: { state: {} } }) - expect(result.output.conditions[0].isFulfilled).toBe(true) - }) - }) -}) From 867e689236615a3ec0a42354b2d173d788a2ff05 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Fri, 27 Feb 2026 07:46:22 +0800 Subject: [PATCH 4/4] Update Condition.ts --- packages/components/nodes/agentflow/Condition/Condition.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/nodes/agentflow/Condition/Condition.ts b/packages/components/nodes/agentflow/Condition/Condition.ts index f87816f8465..28143a18dbf 100644 --- a/packages/components/nodes/agentflow/Condition/Condition.ts +++ b/packages/components/nodes/agentflow/Condition/Condition.ts @@ -8,9 +8,9 @@ import removeMarkdown from 'remove-markdown' */ const unescapeRegexPattern = (escaped: string): string => { return escaped - .replace(/\\\\/g, '\0') // Preserve intentional backslashes - .replace(/\\([[\]*])/g, '$1') // Unescape only: [ ] * - .replace(/\0/g, '\\') // Restore preserved backslashes + .replace(/\\\\/g, '\0') // Preserve intentional backslashes + .replace(/\\([[\]*])/g, '$1') // Unescape only: [ ] * + .replace(/\0/g, '\\') // Restore preserved backslashes } class Condition_Agentflow implements INode {