Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,19 @@ function mockNodeFromType(type: INodeTypeDescription) {
} as never);
}

function mockAiToolNode(typeName: string, typeVersion: number): INodeUi {
return vi.mocked<INodeUi>({ type: typeName, typeVersion } as never);
}

const AI_TOOL_CODEX: INodeTypeDescription = {
name: '',
codex: {
categories: ['AI'],
subcategories: { AI: ['Tools'] },
},
...MOCK_NODE_TYPE_MIXIN,
};

describe('makeOverrideValue', () => {
test.each<[string, ...Parameters<typeof makeOverrideValue>]>([
['null nodeType', makeContext(''), null],
Expand Down Expand Up @@ -149,6 +162,39 @@ describe('makeOverrideValue', () => {
expect(result).toBeDefined();
expect(result?.extraPropValues.description).not.toBeDefined();
});

it('creates an override for a parameter named "name" on an allowed AI tool node', () => {
getNodeType.mockReturnValue(AI_NODE_TYPE);
const result = makeOverrideValue(
makeContext('', 'parameters.name'),
mockNodeFromType(AI_NODE_TYPE),
);

expect(result).not.toBeNull();
expect(result?.type).toEqual('fromAI');
});

describe('legacy tool-name node denylist', () => {
test.each<[string, string, number, boolean]>([
['toolWorkflow v2.0 denied', '@n8n/n8n-nodes-langchain.toolWorkflow', 2.0, false],
['toolWorkflow v2.1 denied', '@n8n/n8n-nodes-langchain.toolWorkflow', 2.1, false],
['toolWorkflow v2.2 allowed', '@n8n/n8n-nodes-langchain.toolWorkflow', 2.2, true],
['toolVectorStore v1 denied', '@n8n/n8n-nodes-langchain.toolVectorStore', 1, false],
['toolVectorStore v1.1 allowed', '@n8n/n8n-nodes-langchain.toolVectorStore', 1.1, true],
])('%s', (_name, typeName, typeVersion, shouldOverride) => {
getNodeType.mockReturnValue(AI_TOOL_CODEX);
const result = makeOverrideValue(
makeContext('', 'parameters.name'),
mockAiToolNode(typeName, typeVersion),
);

if (shouldOverride) {
expect(result).not.toBeNull();
} else {
expect(result).toBeNull();
}
});
});
});

describe('FromAiOverride', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,14 @@ const NODE_DENYLIST = [
'@n8n/n8n-nodes-langchain.toolCode',
'@n8n/n8n-nodes-langchain.toolHttpRequest',
'@n8n/n8n-nodes-langchain.mcpClientTool',
['@n8n/n8n-nodes-langchain.toolWorkflow', 1.2],
// Legacy versions read `parameters.name` at runtime as the tool's identity;
// newer versions derive it from the node name, so $fromAI on that field would
// produce an invalid tool name. Keep these ranges in sync when bumping versions.
['@n8n/n8n-nodes-langchain.toolWorkflow', 2.1],
['@n8n/n8n-nodes-langchain.toolVectorStore', 1],
] as const;

const PATH_DENYLIST = [
'parameters.name',
// this is used in vector store tools
'parameters.toolName',
'parameters.description',
Expand Down
Loading