mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-21 09:53:08 +00:00
Compare commits
1 Commits
v2.38.0
...
feat/creat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a9e3c7ec0 |
20
CHANGELOG.md
20
CHANGELOG.md
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2.39.0] - 2026-03-20
|
||||
|
||||
### Added
|
||||
|
||||
- **`n8n_create_data_table` MCP tool** (Issue #640): Create data tables in n8n via the REST API
|
||||
- `N8nApiClient.createDataTable()` calling `POST /data-tables`
|
||||
- Zod-validated handler with `N8nApiError` handling for structured error responses
|
||||
- TypeScript interfaces matching the n8n OpenAPI spec (`DataTableColumn`, `DataTableColumnResponse`, `DataTable`)
|
||||
- Column types per spec: `string | number | boolean | date | json`
|
||||
- Input validation: `.min(1)` on table name and column names
|
||||
- Tool documentation with examples, use cases, and pitfalls
|
||||
- Requires n8n enterprise or cloud with data tables feature enabled
|
||||
- **`projectId` parameter for `n8n_create_workflow`**: Create workflows directly in a specific team project (enterprise feature)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Health check management tool count**: Updated from 13 to 14 to include `n8n_create_data_table`
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.38.0] - 2026-03-20
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-mcp",
|
||||
"version": "2.38.0",
|
||||
"version": "2.39.0",
|
||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
WebhookRequest,
|
||||
McpToolResponse,
|
||||
ExecutionFilterOptions,
|
||||
ExecutionMode
|
||||
ExecutionMode,
|
||||
} from '../types/n8n-api';
|
||||
import type { TriggerType, TestWorkflowInput } from '../triggers/types';
|
||||
import {
|
||||
@@ -383,6 +383,7 @@ const createWorkflowSchema = z.object({
|
||||
executionTimeout: z.number().optional(),
|
||||
errorWorkflow: z.string().optional(),
|
||||
}).optional(),
|
||||
projectId: z.string().optional(),
|
||||
});
|
||||
|
||||
const updateWorkflowSchema = z.object({
|
||||
@@ -1974,7 +1975,7 @@ export async function handleDiagnostic(request: any, context?: InstanceContext):
|
||||
|
||||
// Check which tools are available
|
||||
const documentationTools = 7; // Base documentation tools (after v2.26.0 consolidation)
|
||||
const managementTools = apiConfigured ? 13 : 0; // Management tools requiring API (includes n8n_deploy_template)
|
||||
const managementTools = apiConfigured ? 14 : 0; // Management tools requiring API (includes n8n_create_data_table)
|
||||
const totalTools = documentationTools + managementTools;
|
||||
|
||||
// Check npm version
|
||||
@@ -2688,3 +2689,58 @@ export async function handleTriggerWebhookWorkflow(args: unknown, context?: Inst
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Data Table Handler
|
||||
// ========================================================================
|
||||
|
||||
const createDataTableSchema = z.object({
|
||||
name: z.string().min(1, 'Table name cannot be empty'),
|
||||
columns: z.array(z.object({
|
||||
name: z.string().min(1, 'Column name cannot be empty'),
|
||||
type: z.enum(['string', 'number', 'boolean', 'date', 'json']).optional(),
|
||||
})).optional(),
|
||||
});
|
||||
|
||||
export async function handleCreateDataTable(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const input = createDataTableSchema.parse(args);
|
||||
const dataTable = await client.createDataTable(input);
|
||||
|
||||
if (!dataTable || !dataTable.id) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Data table creation failed: n8n API returned an empty or invalid response'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: { id: dataTable.id, name: dataTable.name },
|
||||
message: `Data table "${dataTable.name}" created with ID: ${dataTable.id}`
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid input',
|
||||
details: { errors: error.errors }
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
code: error.code,
|
||||
details: error.details as Record<string, unknown> | undefined
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1496,6 +1496,10 @@ export class N8NDocumentationMCPServer {
|
||||
if (!this.repository) throw new Error('Repository not initialized');
|
||||
return n8nHandlers.handleDeployTemplate(args, this.templateService, this.repository, this.instanceContext);
|
||||
|
||||
case 'n8n_create_data_table':
|
||||
this.validateToolParams(name, args, ['name']);
|
||||
return n8nHandlers.handleCreateDataTable(args, this.instanceContext);
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ import {
|
||||
n8nTestWorkflowDoc,
|
||||
n8nExecutionsDoc,
|
||||
n8nWorkflowVersionsDoc,
|
||||
n8nDeployTemplateDoc
|
||||
n8nDeployTemplateDoc,
|
||||
n8nCreateDataTableDoc
|
||||
} from './workflow_management';
|
||||
|
||||
// Combine all tool documentations into a single object
|
||||
@@ -60,7 +61,8 @@ export const toolsDocumentation: Record<string, ToolDocumentation> = {
|
||||
n8n_test_workflow: n8nTestWorkflowDoc,
|
||||
n8n_executions: n8nExecutionsDoc,
|
||||
n8n_workflow_versions: n8nWorkflowVersionsDoc,
|
||||
n8n_deploy_template: n8nDeployTemplateDoc
|
||||
n8n_deploy_template: n8nDeployTemplateDoc,
|
||||
n8n_create_data_table: n8nCreateDataTableDoc
|
||||
};
|
||||
|
||||
// Re-export types
|
||||
|
||||
@@ -10,3 +10,4 @@ export { n8nTestWorkflowDoc } from './n8n-test-workflow';
|
||||
export { n8nExecutionsDoc } from './n8n-executions';
|
||||
export { n8nWorkflowVersionsDoc } from './n8n-workflow-versions';
|
||||
export { n8nDeployTemplateDoc } from './n8n-deploy-template';
|
||||
export { n8nCreateDataTableDoc } from './n8n-create-data-table';
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const n8nCreateDataTableDoc: ToolDocumentation = {
|
||||
name: 'n8n_create_data_table',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Create a new data table in n8n. Requires n8n enterprise or cloud with the data tables feature enabled.',
|
||||
keyParameters: ['name', 'columns'],
|
||||
example: 'n8n_create_data_table({name: "Contacts", columns: [{name: "email", type: "string"}]})',
|
||||
performance: 'Fast (100-300ms)',
|
||||
tips: [
|
||||
'Available column types: string, number, boolean, date, json',
|
||||
'Columns are optional — a table can be created without columns',
|
||||
'Requires n8n enterprise or cloud plan with data tables feature',
|
||||
'projectId cannot be set via the public API — use the n8n UI for project assignment'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: 'Creates a new data table in n8n. Data tables are structured, persistent storage for workflow data. Each table can have typed columns (string, number, boolean, date, json). Requires the data tables feature to be enabled on the n8n instance.',
|
||||
parameters: {
|
||||
name: { type: 'string', required: true, description: 'Name for the new data table' },
|
||||
columns: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
description: 'Column definitions. Each column has a name and optional type (string, number, boolean, date, json). Defaults to string if type is omitted.'
|
||||
}
|
||||
},
|
||||
returns: 'Object with id and name of the created data table on success.',
|
||||
examples: [
|
||||
'n8n_create_data_table({name: "Orders"}) - Create table without columns',
|
||||
'n8n_create_data_table({name: "Contacts", columns: [{name: "email", type: "string"}, {name: "score", type: "number"}]}) - Create table with typed columns',
|
||||
'n8n_create_data_table({name: "Events", columns: [{name: "payload", type: "json"}, {name: "occurred_at", type: "date"}]})'
|
||||
],
|
||||
useCases: [
|
||||
'Persist structured workflow data across executions',
|
||||
'Store lookup tables for workflow logic',
|
||||
'Accumulate records from multiple workflow runs',
|
||||
'Share data between different workflows'
|
||||
],
|
||||
performance: 'Fast operation — typically 100-300ms.',
|
||||
bestPractices: [
|
||||
'Define columns upfront to enforce schema consistency',
|
||||
'Use typed columns for numeric or date data to enable proper filtering',
|
||||
'Use json type for nested or variable-structure data'
|
||||
],
|
||||
pitfalls: [
|
||||
'Requires N8N_API_URL and N8N_API_KEY configured',
|
||||
'Feature only available on n8n enterprise or cloud plans',
|
||||
'projectId cannot be set via the public API',
|
||||
'Column types cannot be changed after table creation via API'
|
||||
],
|
||||
relatedTools: ['n8n_create_workflow', 'n8n_list_workflows', 'n8n_health_check']
|
||||
}
|
||||
};
|
||||
@@ -63,6 +63,10 @@ export const n8nManagementTools: ToolDefinition[] = [
|
||||
executionTimeout: { type: 'number' },
|
||||
errorWorkflow: { type: 'string' }
|
||||
}
|
||||
},
|
||||
projectId: {
|
||||
type: 'string',
|
||||
description: 'Optional project ID to create the workflow in (enterprise feature)'
|
||||
}
|
||||
},
|
||||
required: ['name', 'nodes', 'connections']
|
||||
@@ -602,5 +606,38 @@ export const n8nManagementTools: ToolDefinition[] = [
|
||||
destructiveHint: false,
|
||||
openWorldHint: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n_create_data_table',
|
||||
description: 'Create a new data table in n8n. Requires n8n enterprise or cloud with data tables feature.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Name for the data table' },
|
||||
columns: {
|
||||
type: 'array',
|
||||
description: 'Column definitions',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Column name' },
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: ['string', 'number', 'boolean', 'date', 'json'],
|
||||
description: 'Column data type',
|
||||
},
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
},
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
annotations: {
|
||||
title: 'Create Data Table',
|
||||
readOnlyHint: false,
|
||||
destructiveHint: false,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -22,6 +22,8 @@ import {
|
||||
SourceControlStatus,
|
||||
SourceControlPullResult,
|
||||
SourceControlPushResult,
|
||||
DataTable,
|
||||
DataTableColumn,
|
||||
} from '../types/n8n-api';
|
||||
import { handleN8nApiError, logN8nError } from '../utils/n8n-errors';
|
||||
import { cleanWorkflowForCreate, cleanWorkflowForUpdate } from './n8n-validation';
|
||||
@@ -582,6 +584,15 @@ export class N8nApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
async createDataTable(params: { name: string; columns?: DataTableColumn[] }): Promise<DataTable> {
|
||||
try {
|
||||
const response = await this.client.post('/data-tables', params);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleN8nApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and normalizes n8n API list responses.
|
||||
* Handles both modern format {data: [], nextCursor?: string} and legacy array format.
|
||||
|
||||
@@ -454,4 +454,26 @@ export interface ErrorSuggestion {
|
||||
title: string;
|
||||
description: string;
|
||||
confidence: 'high' | 'medium' | 'low';
|
||||
}
|
||||
|
||||
// Data Table types
|
||||
export interface DataTableColumn {
|
||||
name: string;
|
||||
type?: 'string' | 'number' | 'boolean' | 'date' | 'json';
|
||||
}
|
||||
|
||||
export interface DataTableColumnResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'string' | 'number' | 'boolean' | 'date' | 'json';
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface DataTable {
|
||||
id: string;
|
||||
name: string;
|
||||
columns?: DataTableColumnResponse[];
|
||||
projectId?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
251
tests/unit/mcp/handlers-create-data-table.test.ts
Normal file
251
tests/unit/mcp/handlers-create-data-table.test.ts
Normal file
@@ -0,0 +1,251 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { N8nApiClient } from '@/services/n8n-api-client';
|
||||
import { N8nApiError } from '@/utils/n8n-errors';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('@/services/n8n-api-client');
|
||||
vi.mock('@/config/n8n-api', () => ({
|
||||
getN8nApiConfig: vi.fn(),
|
||||
}));
|
||||
vi.mock('@/services/n8n-validation', () => ({
|
||||
validateWorkflowStructure: vi.fn(),
|
||||
hasWebhookTrigger: vi.fn(),
|
||||
getWebhookUrl: vi.fn(),
|
||||
}));
|
||||
vi.mock('@/utils/logger', () => ({
|
||||
logger: {
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
},
|
||||
Logger: vi.fn().mockImplementation(() => ({
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
})),
|
||||
LogLevel: {
|
||||
ERROR: 0,
|
||||
WARN: 1,
|
||||
INFO: 2,
|
||||
DEBUG: 3,
|
||||
},
|
||||
}));
|
||||
|
||||
describe('handleCreateDataTable', () => {
|
||||
let mockApiClient: any;
|
||||
let handlers: any;
|
||||
let getN8nApiConfig: any;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Setup mock API client
|
||||
mockApiClient = {
|
||||
createWorkflow: vi.fn(),
|
||||
getWorkflow: vi.fn(),
|
||||
updateWorkflow: vi.fn(),
|
||||
deleteWorkflow: vi.fn(),
|
||||
listWorkflows: vi.fn(),
|
||||
triggerWebhook: vi.fn(),
|
||||
getExecution: vi.fn(),
|
||||
listExecutions: vi.fn(),
|
||||
deleteExecution: vi.fn(),
|
||||
healthCheck: vi.fn(),
|
||||
createDataTable: vi.fn(),
|
||||
};
|
||||
|
||||
// Import mocked modules
|
||||
getN8nApiConfig = (await import('@/config/n8n-api')).getN8nApiConfig;
|
||||
|
||||
// Mock the API config
|
||||
vi.mocked(getN8nApiConfig).mockReturnValue({
|
||||
baseUrl: 'https://n8n.test.com',
|
||||
apiKey: 'test-key',
|
||||
timeout: 30000,
|
||||
maxRetries: 3,
|
||||
});
|
||||
|
||||
// Mock the N8nApiClient constructor
|
||||
vi.mocked(N8nApiClient).mockImplementation(() => mockApiClient);
|
||||
|
||||
// Import handlers module after setting up mocks
|
||||
handlers = await import('@/mcp/handlers-n8n-manager');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (handlers) {
|
||||
const clientGetter = handlers.getN8nApiClient;
|
||||
if (clientGetter) {
|
||||
vi.mocked(getN8nApiConfig).mockReturnValue(null);
|
||||
clientGetter();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should create data table with name and columns successfully', async () => {
|
||||
const createdTable = {
|
||||
id: 'dt-123',
|
||||
name: 'My Data Table',
|
||||
columns: [
|
||||
{ id: 'col-1', name: 'email', type: 'string', index: 0 },
|
||||
{ id: 'col-2', name: 'age', type: 'number', index: 1 },
|
||||
],
|
||||
};
|
||||
|
||||
mockApiClient.createDataTable.mockResolvedValue(createdTable);
|
||||
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'My Data Table',
|
||||
columns: [
|
||||
{ name: 'email', type: 'string' },
|
||||
{ name: 'age', type: 'number' },
|
||||
],
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
data: { id: 'dt-123', name: 'My Data Table' },
|
||||
message: 'Data table "My Data Table" created with ID: dt-123',
|
||||
});
|
||||
|
||||
expect(mockApiClient.createDataTable).toHaveBeenCalledWith({
|
||||
name: 'My Data Table',
|
||||
columns: [
|
||||
{ name: 'email', type: 'string' },
|
||||
{ name: 'age', type: 'number' },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should create data table with name only (no columns)', async () => {
|
||||
const createdTable = {
|
||||
id: 'dt-456',
|
||||
name: 'Empty Table',
|
||||
};
|
||||
|
||||
mockApiClient.createDataTable.mockResolvedValue(createdTable);
|
||||
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'Empty Table',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
data: { id: 'dt-456', name: 'Empty Table' },
|
||||
message: 'Data table "Empty Table" created with ID: dt-456',
|
||||
});
|
||||
|
||||
expect(mockApiClient.createDataTable).toHaveBeenCalledWith({
|
||||
name: 'Empty Table',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return error when n8n API is not configured', async () => {
|
||||
vi.mocked(getN8nApiConfig).mockReturnValue(null);
|
||||
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'Test Table',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'n8n API not configured. Please set N8N_API_URL and N8N_API_KEY environment variables.',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return Zod validation error when name is missing', async () => {
|
||||
const result = await handlers.handleCreateDataTable({});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Invalid input');
|
||||
expect(result.details).toHaveProperty('errors');
|
||||
});
|
||||
|
||||
it('should return Zod validation error when name is empty string', async () => {
|
||||
const result = await handlers.handleCreateDataTable({ name: '' });
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Invalid input');
|
||||
expect(result.details).toHaveProperty('errors');
|
||||
});
|
||||
|
||||
it('should return Zod validation error when column name is empty string', async () => {
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'Valid Table',
|
||||
columns: [{ name: '' }],
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Invalid input');
|
||||
expect(result.details).toHaveProperty('errors');
|
||||
});
|
||||
|
||||
it('should return error when API call fails', async () => {
|
||||
const apiError = new Error('Data table creation failed on the server');
|
||||
mockApiClient.createDataTable.mockRejectedValue(apiError);
|
||||
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'Duplicate Table',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'Data table creation failed on the server',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return structured error for N8nApiError', async () => {
|
||||
const apiError = new N8nApiError('Feature not available', 402, 'PAYMENT_REQUIRED', { plan: 'enterprise' });
|
||||
mockApiClient.createDataTable.mockRejectedValue(apiError);
|
||||
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'Enterprise Table',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.code).toBe('PAYMENT_REQUIRED');
|
||||
expect(result.details).toEqual({ plan: 'enterprise' });
|
||||
});
|
||||
|
||||
it('should return error when API returns empty response (null)', async () => {
|
||||
mockApiClient.createDataTable.mockResolvedValue(null);
|
||||
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'Ghost Table',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'Data table creation failed: n8n API returned an empty or invalid response',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return error when API returns response without id', async () => {
|
||||
mockApiClient.createDataTable.mockResolvedValue({ name: 'No ID Table' });
|
||||
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'No ID Table',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'Data table creation failed: n8n API returned an empty or invalid response',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return Unknown error when a non-Error value is thrown', async () => {
|
||||
mockApiClient.createDataTable.mockRejectedValue('string-error');
|
||||
|
||||
const result = await handlers.handleCreateDataTable({
|
||||
name: 'Error Table',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
error: 'Unknown error occurred',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -631,6 +631,27 @@ describe('handlers-n8n-manager', () => {
|
||||
expect(result.details.errors[0]).toContain('Webhook');
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass projectId to API when provided', async () => {
|
||||
const testWorkflow = createTestWorkflow();
|
||||
const input = {
|
||||
name: 'Test Workflow',
|
||||
nodes: testWorkflow.nodes,
|
||||
connections: testWorkflow.connections,
|
||||
projectId: 'project-abc-123',
|
||||
};
|
||||
|
||||
mockApiClient.createWorkflow.mockResolvedValue(testWorkflow);
|
||||
|
||||
const result = await handlers.handleCreateWorkflow(input);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(mockApiClient.createWorkflow).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
projectId: 'project-abc-123',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleGetWorkflow', () => {
|
||||
|
||||
@@ -542,6 +542,9 @@ describe('Parameter Validation', () => {
|
||||
await expect(server.testExecuteTool('n8n_test_workflow', {}))
|
||||
.rejects.toThrow('Missing required parameters for n8n_test_workflow: workflowId');
|
||||
|
||||
await expect(server.testExecuteTool('n8n_create_data_table', {}))
|
||||
.rejects.toThrow('Missing required parameters for n8n_create_data_table: name');
|
||||
|
||||
for (const tool of n8nToolsWithRequiredParams) {
|
||||
await expect(server.testExecuteTool(tool.name, tool.args))
|
||||
.rejects.toThrow(tool.expected);
|
||||
|
||||
@@ -1300,6 +1300,59 @@ describe('N8nApiClient', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('createDataTable', () => {
|
||||
beforeEach(() => {
|
||||
client = new N8nApiClient(defaultConfig);
|
||||
});
|
||||
|
||||
it('should create data table with name and columns', async () => {
|
||||
const params = {
|
||||
name: 'My Table',
|
||||
columns: [
|
||||
{ name: 'email', type: 'string' as const },
|
||||
{ name: 'count', type: 'number' as const },
|
||||
],
|
||||
};
|
||||
const createdTable = { id: 'dt-1', name: 'My Table', columns: [] };
|
||||
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: createdTable });
|
||||
|
||||
const result = await client.createDataTable(params);
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/data-tables', params);
|
||||
expect(result).toEqual(createdTable);
|
||||
});
|
||||
|
||||
it('should create data table without columns', async () => {
|
||||
const params = { name: 'Empty Table' };
|
||||
const createdTable = { id: 'dt-2', name: 'Empty Table' };
|
||||
|
||||
mockAxiosInstance.post.mockResolvedValue({ data: createdTable });
|
||||
|
||||
const result = await client.createDataTable(params);
|
||||
|
||||
expect(mockAxiosInstance.post).toHaveBeenCalledWith('/data-tables', params);
|
||||
expect(result).toEqual(createdTable);
|
||||
});
|
||||
|
||||
it('should handle 400 error', async () => {
|
||||
const error = {
|
||||
message: 'Request failed',
|
||||
response: { status: 400, data: { message: 'Invalid table name' } },
|
||||
};
|
||||
await mockAxiosInstance.simulateError('post', error);
|
||||
|
||||
try {
|
||||
await client.createDataTable({ name: '' });
|
||||
expect.fail('Should have thrown an error');
|
||||
} catch (err) {
|
||||
expect(err).toBeInstanceOf(N8nValidationError);
|
||||
expect((err as N8nValidationError).message).toBe('Invalid table name');
|
||||
expect((err as N8nValidationError).statusCode).toBe(400);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('interceptors', () => {
|
||||
let requestInterceptor: any;
|
||||
let responseInterceptor: any;
|
||||
|
||||
Reference in New Issue
Block a user