mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-01-30 06:22:04 +00:00
- Simplified Calculator and Think tool validators (no toolDescription required - built-in descriptions) - Fixed trigger counting to exclude respondToWebhook from trigger detection - Fixed streaming error filters to use correct error code access pattern (details.code || code) This resolves 9 remaining integration test failures from Phase 2 AI validation implementation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
436 lines
13 KiB
TypeScript
436 lines
13 KiB
TypeScript
/**
|
|
* Integration Tests: AI Agent Validation
|
|
*
|
|
* Tests AI Agent validation against real n8n instance.
|
|
* These tests validate the fixes from v2.17.0 including node type normalization.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, afterEach, afterAll } from 'vitest';
|
|
import { createTestContext, TestContext, createTestWorkflowName } from '../n8n-api/utils/test-context';
|
|
import { getTestN8nClient } from '../n8n-api/utils/n8n-client';
|
|
import { N8nApiClient } from '../../../src/services/n8n-api-client';
|
|
import { cleanupOrphanedWorkflows } from '../n8n-api/utils/cleanup-helpers';
|
|
import { createMcpContext } from '../n8n-api/utils/mcp-context';
|
|
import { InstanceContext } from '../../../src/types/instance-context';
|
|
import { handleValidateWorkflow } from '../../../src/mcp/handlers-n8n-manager';
|
|
import { getNodeRepository, closeNodeRepository } from '../n8n-api/utils/node-repository';
|
|
import { NodeRepository } from '../../../src/database/node-repository';
|
|
import { ValidationResponse } from '../n8n-api/types/mcp-responses';
|
|
import {
|
|
createAIAgentNode,
|
|
createChatTriggerNode,
|
|
createLanguageModelNode,
|
|
createHTTPRequestToolNode,
|
|
createCodeToolNode,
|
|
createMemoryNode,
|
|
createRespondNode,
|
|
createAIConnection,
|
|
createMainConnection,
|
|
mergeConnections,
|
|
createAIWorkflow
|
|
} from './helpers';
|
|
|
|
describe('Integration: AI Agent Validation', () => {
|
|
let context: TestContext;
|
|
let client: N8nApiClient;
|
|
let mcpContext: InstanceContext;
|
|
let repository: NodeRepository;
|
|
|
|
beforeEach(async () => {
|
|
context = createTestContext();
|
|
client = getTestN8nClient();
|
|
mcpContext = createMcpContext();
|
|
repository = await getNodeRepository();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await context.cleanup();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await closeNodeRepository();
|
|
if (!process.env.CI) {
|
|
await cleanupOrphanedWorkflows();
|
|
}
|
|
});
|
|
|
|
// ======================================================================
|
|
// TEST 1: Missing Language Model
|
|
// ======================================================================
|
|
|
|
it('should detect missing language model in real workflow', async () => {
|
|
const agent = createAIAgentNode({
|
|
name: 'AI Agent',
|
|
text: 'Test prompt'
|
|
});
|
|
|
|
const workflow = createAIWorkflow(
|
|
[agent],
|
|
{},
|
|
{
|
|
name: createTestWorkflowName('AI Agent - Missing Model'),
|
|
tags: ['mcp-integration-test', 'ai-validation']
|
|
}
|
|
);
|
|
|
|
const created = await client.createWorkflow(workflow);
|
|
context.trackWorkflow(created.id!);
|
|
|
|
const response = await handleValidateWorkflow(
|
|
{ id: created.id },
|
|
repository,
|
|
mcpContext
|
|
);
|
|
|
|
expect(response.success).toBe(true);
|
|
const data = response.data as ValidationResponse;
|
|
|
|
expect(data.valid).toBe(false);
|
|
expect(data.errors).toBeDefined();
|
|
expect(data.errors!.length).toBeGreaterThan(0);
|
|
|
|
const errorCodes = data.errors!.map(e => e.details?.code || e.code);
|
|
expect(errorCodes).toContain('MISSING_LANGUAGE_MODEL');
|
|
|
|
const errorMessages = data.errors!.map(e => e.message).join(' ');
|
|
expect(errorMessages).toMatch(/language model|ai_languageModel/i);
|
|
});
|
|
|
|
// ======================================================================
|
|
// TEST 2: Valid AI Agent with Language Model
|
|
// ======================================================================
|
|
|
|
it('should validate AI Agent with language model', async () => {
|
|
const languageModel = createLanguageModelNode('openai', {
|
|
name: 'OpenAI Chat Model'
|
|
});
|
|
|
|
const agent = createAIAgentNode({
|
|
name: 'AI Agent',
|
|
text: 'You are a helpful assistant'
|
|
});
|
|
|
|
const workflow = createAIWorkflow(
|
|
[languageModel, agent],
|
|
mergeConnections(
|
|
createAIConnection('OpenAI Chat Model', 'AI Agent', 'ai_languageModel')
|
|
),
|
|
{
|
|
name: createTestWorkflowName('AI Agent - Valid'),
|
|
tags: ['mcp-integration-test', 'ai-validation']
|
|
}
|
|
);
|
|
|
|
const created = await client.createWorkflow(workflow);
|
|
context.trackWorkflow(created.id!);
|
|
|
|
const response = await handleValidateWorkflow(
|
|
{ id: created.id },
|
|
repository,
|
|
mcpContext
|
|
);
|
|
|
|
expect(response.success).toBe(true);
|
|
const data = response.data as ValidationResponse;
|
|
|
|
expect(data.valid).toBe(true);
|
|
expect(data.errors).toBeUndefined();
|
|
expect(data.summary.errorCount).toBe(0);
|
|
});
|
|
|
|
// ======================================================================
|
|
// TEST 3: Tool Connections Detection
|
|
// ======================================================================
|
|
|
|
it('should detect tool connections correctly', async () => {
|
|
const languageModel = createLanguageModelNode('openai', {
|
|
name: 'OpenAI Chat Model'
|
|
});
|
|
|
|
const httpTool = createHTTPRequestToolNode({
|
|
name: 'HTTP Request Tool',
|
|
toolDescription: 'Fetches weather data from API',
|
|
url: 'https://api.weather.com/current',
|
|
method: 'GET'
|
|
});
|
|
|
|
const agent = createAIAgentNode({
|
|
name: 'AI Agent',
|
|
text: 'You are a weather assistant'
|
|
});
|
|
|
|
const workflow = createAIWorkflow(
|
|
[languageModel, httpTool, agent],
|
|
mergeConnections(
|
|
createAIConnection('OpenAI Chat Model', 'AI Agent', 'ai_languageModel'),
|
|
createAIConnection('HTTP Request Tool', 'AI Agent', 'ai_tool')
|
|
),
|
|
{
|
|
name: createTestWorkflowName('AI Agent - With Tool'),
|
|
tags: ['mcp-integration-test', 'ai-validation']
|
|
}
|
|
);
|
|
|
|
const created = await client.createWorkflow(workflow);
|
|
context.trackWorkflow(created.id!);
|
|
|
|
const response = await handleValidateWorkflow(
|
|
{ id: created.id },
|
|
repository,
|
|
mcpContext
|
|
);
|
|
|
|
expect(response.success).toBe(true);
|
|
const data = response.data as ValidationResponse;
|
|
|
|
expect(data.valid).toBe(true);
|
|
|
|
// Should NOT have false "no tools" warning
|
|
if (data.warnings) {
|
|
const toolWarnings = data.warnings.filter(w =>
|
|
w.message.toLowerCase().includes('no ai_tool')
|
|
);
|
|
expect(toolWarnings.length).toBe(0);
|
|
}
|
|
});
|
|
|
|
// ======================================================================
|
|
// TEST 4: Streaming Mode Constraints (Chat Trigger)
|
|
// ======================================================================
|
|
|
|
it('should validate streaming mode constraints', async () => {
|
|
const chatTrigger = createChatTriggerNode({
|
|
name: 'Chat Trigger',
|
|
responseMode: 'streaming'
|
|
});
|
|
|
|
const languageModel = createLanguageModelNode('openai', {
|
|
name: 'OpenAI Chat Model'
|
|
});
|
|
|
|
const agent = createAIAgentNode({
|
|
name: 'AI Agent',
|
|
text: 'You are a helpful assistant'
|
|
});
|
|
|
|
const respond = createRespondNode({
|
|
name: 'Respond to Webhook'
|
|
});
|
|
|
|
const workflow = createAIWorkflow(
|
|
[chatTrigger, languageModel, agent, respond],
|
|
mergeConnections(
|
|
createMainConnection('Chat Trigger', 'AI Agent'),
|
|
createAIConnection('OpenAI Chat Model', 'AI Agent', 'ai_languageModel'),
|
|
createMainConnection('AI Agent', 'Respond to Webhook') // ERROR: streaming with main output
|
|
),
|
|
{
|
|
name: createTestWorkflowName('AI Agent - Streaming Error'),
|
|
tags: ['mcp-integration-test', 'ai-validation']
|
|
}
|
|
);
|
|
|
|
const created = await client.createWorkflow(workflow);
|
|
context.trackWorkflow(created.id!);
|
|
|
|
const response = await handleValidateWorkflow(
|
|
{ id: created.id },
|
|
repository,
|
|
mcpContext
|
|
);
|
|
|
|
expect(response.success).toBe(true);
|
|
const data = response.data as ValidationResponse;
|
|
|
|
expect(data.valid).toBe(false);
|
|
expect(data.errors).toBeDefined();
|
|
|
|
const streamingErrors = data.errors!.filter(e => {
|
|
const code = e.details?.code || e.code;
|
|
return code === 'STREAMING_WITH_MAIN_OUTPUT' ||
|
|
code === 'STREAMING_AGENT_HAS_OUTPUT';
|
|
});
|
|
expect(streamingErrors.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
// ======================================================================
|
|
// TEST 5: AI Agent Own streamResponse Setting
|
|
// ======================================================================
|
|
|
|
it('should validate AI Agent own streamResponse setting', async () => {
|
|
const languageModel = createLanguageModelNode('openai', {
|
|
name: 'OpenAI Chat Model'
|
|
});
|
|
|
|
const agent = createAIAgentNode({
|
|
name: 'AI Agent',
|
|
text: 'You are a helpful assistant',
|
|
streamResponse: true // Agent has its own streaming enabled
|
|
});
|
|
|
|
const respond = createRespondNode({
|
|
name: 'Respond to Webhook'
|
|
});
|
|
|
|
const workflow = createAIWorkflow(
|
|
[languageModel, agent, respond],
|
|
mergeConnections(
|
|
createAIConnection('OpenAI Chat Model', 'AI Agent', 'ai_languageModel'),
|
|
createMainConnection('AI Agent', 'Respond to Webhook') // ERROR: streaming with main output
|
|
),
|
|
{
|
|
name: createTestWorkflowName('AI Agent - Own Streaming'),
|
|
tags: ['mcp-integration-test', 'ai-validation']
|
|
}
|
|
);
|
|
|
|
const created = await client.createWorkflow(workflow);
|
|
context.trackWorkflow(created.id!);
|
|
|
|
const response = await handleValidateWorkflow(
|
|
{ id: created.id },
|
|
repository,
|
|
mcpContext
|
|
);
|
|
|
|
expect(response.success).toBe(true);
|
|
const data = response.data as ValidationResponse;
|
|
|
|
expect(data.valid).toBe(false);
|
|
expect(data.errors).toBeDefined();
|
|
|
|
const errorCodes = data.errors!.map(e => e.details?.code || e.code);
|
|
expect(errorCodes).toContain('STREAMING_WITH_MAIN_OUTPUT');
|
|
});
|
|
|
|
// ======================================================================
|
|
// TEST 6: Multiple Memory Connections
|
|
// ======================================================================
|
|
|
|
it('should validate memory connections', async () => {
|
|
const languageModel = createLanguageModelNode('openai', {
|
|
name: 'OpenAI Chat Model'
|
|
});
|
|
|
|
const memory1 = createMemoryNode({
|
|
name: 'Memory 1'
|
|
});
|
|
|
|
const memory2 = createMemoryNode({
|
|
name: 'Memory 2'
|
|
});
|
|
|
|
const agent = createAIAgentNode({
|
|
name: 'AI Agent',
|
|
text: 'You are a helpful assistant'
|
|
});
|
|
|
|
const workflow = createAIWorkflow(
|
|
[languageModel, memory1, memory2, agent],
|
|
mergeConnections(
|
|
createAIConnection('OpenAI Chat Model', 'AI Agent', 'ai_languageModel'),
|
|
createAIConnection('Memory 1', 'AI Agent', 'ai_memory'),
|
|
createAIConnection('Memory 2', 'AI Agent', 'ai_memory') // ERROR: multiple memory
|
|
),
|
|
{
|
|
name: createTestWorkflowName('AI Agent - Multiple Memory'),
|
|
tags: ['mcp-integration-test', 'ai-validation']
|
|
}
|
|
);
|
|
|
|
const created = await client.createWorkflow(workflow);
|
|
context.trackWorkflow(created.id!);
|
|
|
|
const response = await handleValidateWorkflow(
|
|
{ id: created.id },
|
|
repository,
|
|
mcpContext
|
|
);
|
|
|
|
expect(response.success).toBe(true);
|
|
const data = response.data as ValidationResponse;
|
|
|
|
expect(data.valid).toBe(false);
|
|
expect(data.errors).toBeDefined();
|
|
|
|
const errorCodes = data.errors!.map(e => e.details?.code || e.code);
|
|
expect(errorCodes).toContain('MULTIPLE_MEMORY_CONNECTIONS');
|
|
});
|
|
|
|
// ======================================================================
|
|
// TEST 7: Complete AI Workflow (All Components)
|
|
// ======================================================================
|
|
|
|
it('should validate complete AI workflow', async () => {
|
|
const chatTrigger = createChatTriggerNode({
|
|
name: 'Chat Trigger',
|
|
responseMode: 'lastNode' // Not streaming
|
|
});
|
|
|
|
const languageModel = createLanguageModelNode('openai', {
|
|
name: 'OpenAI Chat Model'
|
|
});
|
|
|
|
const httpTool = createHTTPRequestToolNode({
|
|
name: 'HTTP Request Tool',
|
|
toolDescription: 'Fetches data from external API',
|
|
url: 'https://api.example.com/data',
|
|
method: 'GET'
|
|
});
|
|
|
|
const codeTool = createCodeToolNode({
|
|
name: 'Code Tool',
|
|
toolDescription: 'Processes data with custom logic',
|
|
code: 'return { result: "processed" };'
|
|
});
|
|
|
|
const memory = createMemoryNode({
|
|
name: 'Window Buffer Memory',
|
|
contextWindowLength: 5
|
|
});
|
|
|
|
const agent = createAIAgentNode({
|
|
name: 'AI Agent',
|
|
promptType: 'define',
|
|
text: 'You are a helpful assistant with access to tools',
|
|
systemMessage: 'You are an AI assistant that helps users with data processing and external API calls.'
|
|
});
|
|
|
|
const respond = createRespondNode({
|
|
name: 'Respond to Webhook'
|
|
});
|
|
|
|
const workflow = createAIWorkflow(
|
|
[chatTrigger, languageModel, httpTool, codeTool, memory, agent, respond],
|
|
mergeConnections(
|
|
createMainConnection('Chat Trigger', 'AI Agent'),
|
|
createAIConnection('OpenAI Chat Model', 'AI Agent', 'ai_languageModel'),
|
|
createAIConnection('HTTP Request Tool', 'AI Agent', 'ai_tool'),
|
|
createAIConnection('Code Tool', 'AI Agent', 'ai_tool'),
|
|
createAIConnection('Window Buffer Memory', 'AI Agent', 'ai_memory'),
|
|
createMainConnection('AI Agent', 'Respond to Webhook')
|
|
),
|
|
{
|
|
name: createTestWorkflowName('AI Agent - Complete Workflow'),
|
|
tags: ['mcp-integration-test', 'ai-validation']
|
|
}
|
|
);
|
|
|
|
const created = await client.createWorkflow(workflow);
|
|
context.trackWorkflow(created.id!);
|
|
|
|
const response = await handleValidateWorkflow(
|
|
{ id: created.id },
|
|
repository,
|
|
mcpContext
|
|
);
|
|
|
|
expect(response.success).toBe(true);
|
|
const data = response.data as ValidationResponse;
|
|
|
|
expect(data.valid).toBe(true);
|
|
expect(data.errors).toBeUndefined();
|
|
expect(data.summary.errorCount).toBe(0);
|
|
});
|
|
});
|