mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-19 08:53:09 +00:00
Compare commits
7 Commits
v2.22.18
...
fix/issue-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df816556e5 | ||
|
|
dbd1406c43 | ||
|
|
a78d7404c0 | ||
|
|
f54d8b6caa | ||
|
|
d8a08947fa | ||
|
|
47d9f55dc5 | ||
|
|
5575630711 |
53
CHANGELOG.md
53
CHANGELOG.md
@@ -7,6 +7,59 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [2.22.21] - 2025-11-20
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
**Fix Empty Settings Object Validation Error (#431)**
|
||||||
|
|
||||||
|
Fixed critical bug where `n8n_update_partial_workflow` tool failed with "request/body must NOT have additional properties" error when workflows had no settings or only non-whitelisted settings properties.
|
||||||
|
|
||||||
|
#### Root Cause
|
||||||
|
- `cleanWorkflowForUpdate()` in `src/services/n8n-validation.ts` was sending empty `settings: {}` objects to the n8n API
|
||||||
|
- n8n API rejects empty settings objects as "additional properties" violation
|
||||||
|
- Issue occurred when:
|
||||||
|
- Workflow had no settings property
|
||||||
|
- Workflow had only non-whitelisted settings (e.g., only `callerPolicy`)
|
||||||
|
|
||||||
|
#### Changes
|
||||||
|
- **Primary Fix**: Modified `cleanWorkflowForUpdate()` to delete `settings` property when empty after filtering
|
||||||
|
- Instead of sending `settings: {}`, the property is now omitted entirely
|
||||||
|
- Added safeguards in lines 193-199 and 201-204
|
||||||
|
- **Secondary Fix**: Enhanced `applyUpdateSettings()` in `workflow-diff-engine.ts` to prevent creating empty settings objects
|
||||||
|
- Only creates/updates settings if operation provides actual properties
|
||||||
|
- **Test Updates**: Fixed 3 incorrect tests that expected empty settings objects
|
||||||
|
- Updated to expect settings property to be omitted instead
|
||||||
|
- Added 2 new comprehensive tests for edge cases
|
||||||
|
|
||||||
|
#### Testing
|
||||||
|
- All 75 unit tests in `n8n-validation.test.ts` passing
|
||||||
|
- New tests cover:
|
||||||
|
- Workflows with no settings → omits property
|
||||||
|
- Workflows with only non-whitelisted settings → omits property
|
||||||
|
- Workflows with mixed settings → keeps only whitelisted properties
|
||||||
|
|
||||||
|
**Related Issues**: #431, #248 (n8n API design limitation)
|
||||||
|
**Related n8n Issue**: n8n-io/n8n#19587 (closed as NOT_PLANNED - MCP server issue)
|
||||||
|
|
||||||
|
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||||
|
|
||||||
|
## [2.22.20] - 2025-11-19
|
||||||
|
|
||||||
|
### 🔄 Dependencies
|
||||||
|
|
||||||
|
**n8n Update to 1.120.3**
|
||||||
|
|
||||||
|
Updated all n8n-related dependencies to their latest versions:
|
||||||
|
|
||||||
|
- n8n: 1.119.1 → 1.120.3
|
||||||
|
- n8n-core: 1.118.0 → 1.119.2
|
||||||
|
- n8n-workflow: 1.116.0 → 1.117.0
|
||||||
|
- @n8n/n8n-nodes-langchain: 1.118.0 → 1.119.1
|
||||||
|
- Rebuilt node database with 544 nodes (439 from n8n-nodes-base, 105 from @n8n/n8n-nodes-langchain)
|
||||||
|
|
||||||
|
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||||
|
|
||||||
## [2.22.18] - 2025-11-14
|
## [2.22.18] - 2025-11-14
|
||||||
|
|
||||||
### ✨ Features
|
### ✨ Features
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
[](https://www.npmjs.com/package/n8n-mcp)
|
[](https://www.npmjs.com/package/n8n-mcp)
|
||||||
[](https://codecov.io/gh/czlonkowski/n8n-mcp)
|
[](https://codecov.io/gh/czlonkowski/n8n-mcp)
|
||||||
[](https://github.com/czlonkowski/n8n-mcp/actions)
|
[](https://github.com/czlonkowski/n8n-mcp/actions)
|
||||||
[](https://github.com/n8n-io/n8n)
|
[](https://github.com/n8n-io/n8n)
|
||||||
[](https://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp)
|
[](https://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp)
|
||||||
[](https://railway.com/deploy/n8n-mcp?referralCode=n8n-mcp)
|
[](https://railway.com/deploy/n8n-mcp?referralCode=n8n-mcp)
|
||||||
|
|
||||||
@@ -1114,6 +1114,13 @@ Current database coverage (n8n v1.117.2):
|
|||||||
|
|
||||||
## 🔄 Recent Updates
|
## 🔄 Recent Updates
|
||||||
|
|
||||||
|
### v2.22.19 - Critical Bug Fix
|
||||||
|
**Fixed:** Stack overflow in session removal (Issue #427)
|
||||||
|
- Eliminated infinite recursion in HTTP server session cleanup
|
||||||
|
- Transport resources now deleted before closing to prevent circular event handler chain
|
||||||
|
- Production logs no longer show "RangeError: Maximum call stack size exceeded"
|
||||||
|
- All session cleanup operations now complete successfully without crashes
|
||||||
|
|
||||||
See [CHANGELOG.md](./docs/CHANGELOG.md) for full version history and recent changes.
|
See [CHANGELOG.md](./docs/CHANGELOG.md) for full version history and recent changes.
|
||||||
|
|
||||||
## ⚠️ Known Issues
|
## ⚠️ Known Issues
|
||||||
|
|||||||
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
1783
package-lock.json
generated
1783
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "n8n-mcp",
|
"name": "n8n-mcp",
|
||||||
"version": "2.22.18",
|
"version": "2.22.21",
|
||||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
@@ -140,15 +140,15 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||||
"@n8n/n8n-nodes-langchain": "^1.118.0",
|
"@n8n/n8n-nodes-langchain": "^1.119.1",
|
||||||
"@supabase/supabase-js": "^2.57.4",
|
"@supabase/supabase-js": "^2.57.4",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"express-rate-limit": "^7.1.5",
|
"express-rate-limit": "^7.1.5",
|
||||||
"lru-cache": "^11.2.1",
|
"lru-cache": "^11.2.1",
|
||||||
"n8n": "^1.119.1",
|
"n8n": "^1.120.3",
|
||||||
"n8n-core": "^1.118.0",
|
"n8n-core": "^1.119.2",
|
||||||
"n8n-workflow": "^1.116.0",
|
"n8n-workflow": "^1.117.0",
|
||||||
"openai": "^4.77.0",
|
"openai": "^4.77.0",
|
||||||
"sql.js": "^1.13.0",
|
"sql.js": "^1.13.0",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
|
|||||||
@@ -155,17 +155,22 @@ export class SingleSessionHTTPServer {
|
|||||||
*/
|
*/
|
||||||
private async removeSession(sessionId: string, reason: string): Promise<void> {
|
private async removeSession(sessionId: string, reason: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Close transport if exists
|
// Store reference to transport before deletion
|
||||||
if (this.transports[sessionId]) {
|
const transport = this.transports[sessionId];
|
||||||
await this.transports[sessionId].close();
|
|
||||||
delete this.transports[sessionId];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove server, metadata, and context
|
// Delete transport FIRST to prevent onclose handler from triggering recursion
|
||||||
|
// This breaks the circular reference: removeSession -> close -> onclose -> removeSession
|
||||||
|
delete this.transports[sessionId];
|
||||||
delete this.servers[sessionId];
|
delete this.servers[sessionId];
|
||||||
delete this.sessionMetadata[sessionId];
|
delete this.sessionMetadata[sessionId];
|
||||||
delete this.sessionContexts[sessionId];
|
delete this.sessionContexts[sessionId];
|
||||||
|
|
||||||
|
// Close transport AFTER deletion
|
||||||
|
// When onclose handler fires, it won't find the transport anymore
|
||||||
|
if (transport) {
|
||||||
|
await transport.close();
|
||||||
|
}
|
||||||
|
|
||||||
logger.info('Session removed', { sessionId, reason });
|
logger.info('Session removed', { sessionId, reason });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn('Error removing session', { sessionId, reason, error });
|
logger.warn('Error removing session', { sessionId, reason, error });
|
||||||
|
|||||||
@@ -103,7 +103,8 @@ export function cleanWorkflowForCreate(workflow: Partial<Workflow>): Partial<Wor
|
|||||||
} = workflow;
|
} = workflow;
|
||||||
|
|
||||||
// Ensure settings are present with defaults
|
// Ensure settings are present with defaults
|
||||||
if (!cleanedWorkflow.settings) {
|
// Treat empty settings object {} the same as missing settings
|
||||||
|
if (!cleanedWorkflow.settings || Object.keys(cleanedWorkflow.settings).length === 0) {
|
||||||
cleanedWorkflow.settings = defaultWorkflowSettings;
|
cleanedWorkflow.settings = defaultWorkflowSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +140,7 @@ export function cleanWorkflowForUpdate(workflow: Workflow): Partial<Workflow> {
|
|||||||
// Remove fields that cause API errors
|
// Remove fields that cause API errors
|
||||||
pinData,
|
pinData,
|
||||||
tags,
|
tags,
|
||||||
|
description, // Issue #431: n8n returns this field but rejects it in updates
|
||||||
// Remove additional fields that n8n API doesn't accept
|
// Remove additional fields that n8n API doesn't accept
|
||||||
isArchived,
|
isArchived,
|
||||||
usedCredentials,
|
usedCredentials,
|
||||||
@@ -155,16 +157,17 @@ export function cleanWorkflowForUpdate(workflow: Workflow): Partial<Workflow> {
|
|||||||
//
|
//
|
||||||
// PROBLEM:
|
// PROBLEM:
|
||||||
// - Some versions reject updates with settings properties (community forum reports)
|
// - Some versions reject updates with settings properties (community forum reports)
|
||||||
// - Cloud versions REQUIRE settings property to be present (n8n.estyl.team)
|
|
||||||
// - Properties like callerPolicy cause "additional properties" errors
|
// - Properties like callerPolicy cause "additional properties" errors
|
||||||
|
// - Empty settings objects {} cause "additional properties" validation errors (Issue #431)
|
||||||
//
|
//
|
||||||
// SOLUTION:
|
// SOLUTION:
|
||||||
// - Filter settings to only include whitelisted properties (OpenAPI spec)
|
// - Filter settings to only include whitelisted properties (OpenAPI spec)
|
||||||
// - If no settings provided, use empty object {} for safety
|
// - If no settings after filtering, omit the property entirely (n8n API rejects empty objects)
|
||||||
// - Empty object satisfies "required property" validation (cloud API)
|
// - Omitting the property prevents "additional properties" validation errors
|
||||||
// - Whitelisted properties prevent "additional properties" errors
|
// - Whitelisted properties prevent "additional properties" errors
|
||||||
//
|
//
|
||||||
// References:
|
// References:
|
||||||
|
// - Issue #431: Empty settings validation error
|
||||||
// - https://community.n8n.io/t/api-workflow-update-endpoint-doesnt-support-setting-callerpolicy/161916
|
// - https://community.n8n.io/t/api-workflow-update-endpoint-doesnt-support-setting-callerpolicy/161916
|
||||||
// - OpenAPI spec: workflowSettings schema
|
// - OpenAPI spec: workflowSettings schema
|
||||||
// - Tested on n8n.estyl.team (cloud) and localhost (self-hosted)
|
// - Tested on n8n.estyl.team (cloud) and localhost (self-hosted)
|
||||||
@@ -189,10 +192,19 @@ export function cleanWorkflowForUpdate(workflow: Workflow): Partial<Workflow> {
|
|||||||
filteredSettings[key] = (cleanedWorkflow.settings as any)[key];
|
filteredSettings[key] = (cleanedWorkflow.settings as any)[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cleanedWorkflow.settings = filteredSettings;
|
|
||||||
|
// n8n API requires settings to be present but rejects empty settings objects.
|
||||||
|
// If no valid properties remain after filtering, include minimal default settings.
|
||||||
|
if (Object.keys(filteredSettings).length > 0) {
|
||||||
|
cleanedWorkflow.settings = filteredSettings;
|
||||||
|
} else {
|
||||||
|
// Provide minimal valid settings (executionOrder v1 is the modern default)
|
||||||
|
cleanedWorkflow.settings = { executionOrder: 'v1' as const };
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// No settings provided - use empty object for safety
|
// No settings provided - include minimal default settings
|
||||||
cleanedWorkflow.settings = {};
|
// n8n API requires settings in workflow updates (v1 is the modern default)
|
||||||
|
cleanedWorkflow.settings = { executionOrder: 'v1' as const };
|
||||||
}
|
}
|
||||||
|
|
||||||
return cleanedWorkflow;
|
return cleanedWorkflow;
|
||||||
|
|||||||
@@ -861,10 +861,14 @@ export class WorkflowDiffEngine {
|
|||||||
|
|
||||||
// Metadata operation appliers
|
// Metadata operation appliers
|
||||||
private applyUpdateSettings(workflow: Workflow, operation: UpdateSettingsOperation): void {
|
private applyUpdateSettings(workflow: Workflow, operation: UpdateSettingsOperation): void {
|
||||||
if (!workflow.settings) {
|
// Only create/update settings if operation provides actual properties
|
||||||
workflow.settings = {};
|
// This prevents creating empty settings objects that would be rejected by n8n API
|
||||||
|
if (operation.settings && Object.keys(operation.settings).length > 0) {
|
||||||
|
if (!workflow.settings) {
|
||||||
|
workflow.settings = {};
|
||||||
|
}
|
||||||
|
Object.assign(workflow.settings, operation.settings);
|
||||||
}
|
}
|
||||||
Object.assign(workflow.settings, operation.settings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyUpdateName(workflow: Workflow, operation: UpdateNameOperation): void {
|
private applyUpdateName(workflow: Workflow, operation: UpdateNameOperation): void {
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export interface WorkflowSettings {
|
|||||||
export interface Workflow {
|
export interface Workflow {
|
||||||
id?: string;
|
id?: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
description?: string; // Returned by GET but must be excluded from PUT/PATCH (n8n API limitation, Issue #431)
|
||||||
nodes: WorkflowNode[];
|
nodes: WorkflowNode[];
|
||||||
connections: WorkflowConnection;
|
connections: WorkflowConnection;
|
||||||
active?: boolean; // Optional for creation as it's read-only
|
active?: boolean; // Optional for creation as it's read-only
|
||||||
|
|||||||
@@ -434,6 +434,62 @@ describe('HTTP Server Session Management', () => {
|
|||||||
// Note: The actual implementation may handle errors differently, so let's verify what we can
|
// Note: The actual implementation may handle errors differently, so let's verify what we can
|
||||||
expect(mockTransport.close).toHaveBeenCalledWith();
|
expect(mockTransport.close).toHaveBeenCalledWith();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not cause infinite recursion when transport.close triggers onclose handler', async () => {
|
||||||
|
server = new SingleSessionHTTPServer();
|
||||||
|
|
||||||
|
const sessionId = 'test-recursion-session';
|
||||||
|
let closeCallCount = 0;
|
||||||
|
let oncloseCallCount = 0;
|
||||||
|
|
||||||
|
// Create a mock transport that simulates the actual behavior
|
||||||
|
const mockTransport = {
|
||||||
|
close: vi.fn().mockImplementation(async () => {
|
||||||
|
closeCallCount++;
|
||||||
|
// Simulate the actual SDK behavior: close() triggers onclose handler
|
||||||
|
if (mockTransport.onclose) {
|
||||||
|
oncloseCallCount++;
|
||||||
|
await mockTransport.onclose();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
onclose: null as (() => Promise<void>) | null,
|
||||||
|
sessionId
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up the transport and session data
|
||||||
|
(server as any).transports = { [sessionId]: mockTransport };
|
||||||
|
(server as any).servers = { [sessionId]: {} };
|
||||||
|
(server as any).sessionMetadata = {
|
||||||
|
[sessionId]: {
|
||||||
|
lastAccess: new Date(),
|
||||||
|
createdAt: new Date()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up onclose handler like the real implementation does
|
||||||
|
// This handler calls removeSession, which could cause infinite recursion
|
||||||
|
mockTransport.onclose = async () => {
|
||||||
|
await (server as any).removeSession(sessionId, 'transport_closed');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call removeSession - this should NOT cause infinite recursion
|
||||||
|
await (server as any).removeSession(sessionId, 'manual_removal');
|
||||||
|
|
||||||
|
// Verify the fix works:
|
||||||
|
// 1. close() should be called exactly once
|
||||||
|
expect(closeCallCount).toBe(1);
|
||||||
|
|
||||||
|
// 2. onclose handler should be triggered
|
||||||
|
expect(oncloseCallCount).toBe(1);
|
||||||
|
|
||||||
|
// 3. Transport should be deleted and not cause second close attempt
|
||||||
|
expect((server as any).transports[sessionId]).toBeUndefined();
|
||||||
|
expect((server as any).servers[sessionId]).toBeUndefined();
|
||||||
|
expect((server as any).sessionMetadata[sessionId]).toBeUndefined();
|
||||||
|
|
||||||
|
// 4. If there was a recursion bug, closeCallCount would be > 1
|
||||||
|
// or the test would timeout/crash with "Maximum call stack size exceeded"
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Session Metadata Tracking', () => {
|
describe('Session Metadata Tracking', () => {
|
||||||
|
|||||||
@@ -367,7 +367,23 @@ describe('n8n-validation', () => {
|
|||||||
expect(cleaned.name).toBe('Test Workflow');
|
expect(cleaned.name).toBe('Test Workflow');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add empty settings object for cloud API compatibility', () => {
|
it('should exclude description field for n8n API compatibility (Issue #431)', () => {
|
||||||
|
const workflow = {
|
||||||
|
name: 'Test Workflow',
|
||||||
|
description: 'This is a test workflow description',
|
||||||
|
nodes: [],
|
||||||
|
connections: {},
|
||||||
|
versionId: 'v123',
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const cleaned = cleanWorkflowForUpdate(workflow);
|
||||||
|
|
||||||
|
expect(cleaned).not.toHaveProperty('description');
|
||||||
|
expect(cleaned).not.toHaveProperty('versionId');
|
||||||
|
expect(cleaned.name).toBe('Test Workflow');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide minimal default settings when no settings provided (Issue #431)', () => {
|
||||||
const workflow = {
|
const workflow = {
|
||||||
name: 'Test Workflow',
|
name: 'Test Workflow',
|
||||||
nodes: [],
|
nodes: [],
|
||||||
@@ -375,7 +391,8 @@ describe('n8n-validation', () => {
|
|||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
const cleaned = cleanWorkflowForUpdate(workflow);
|
const cleaned = cleanWorkflowForUpdate(workflow);
|
||||||
expect(cleaned.settings).toEqual({});
|
// n8n API requires settings to be present, so we provide minimal defaults (v1 is modern default)
|
||||||
|
expect(cleaned.settings).toEqual({ executionOrder: 'v1' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should filter settings to safe properties to prevent API errors (Issue #248 - final fix)', () => {
|
it('should filter settings to safe properties to prevent API errors (Issue #248 - final fix)', () => {
|
||||||
@@ -467,7 +484,50 @@ describe('n8n-validation', () => {
|
|||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
const cleaned = cleanWorkflowForUpdate(workflow);
|
const cleaned = cleanWorkflowForUpdate(workflow);
|
||||||
expect(cleaned.settings).toEqual({});
|
// n8n API requires settings, so we provide minimal defaults (v1 is modern default)
|
||||||
|
expect(cleaned.settings).toEqual({ executionOrder: 'v1' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide minimal settings when only non-whitelisted properties exist (Issue #431)', () => {
|
||||||
|
const workflow = {
|
||||||
|
name: 'Test Workflow',
|
||||||
|
nodes: [],
|
||||||
|
connections: {},
|
||||||
|
settings: {
|
||||||
|
callerPolicy: 'workflowsFromSameOwner' as const, // Filtered out
|
||||||
|
timeSavedPerExecution: 5, // Filtered out (UI-only)
|
||||||
|
someOtherProperty: 'value', // Filtered out
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const cleaned = cleanWorkflowForUpdate(workflow);
|
||||||
|
// All properties were filtered out, but n8n API requires settings
|
||||||
|
// so we provide minimal defaults (v1 is modern default) to avoid both
|
||||||
|
// "additional properties" and "required property" API errors
|
||||||
|
expect(cleaned.settings).toEqual({ executionOrder: 'v1' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preserve whitelisted settings when mixed with non-whitelisted (Issue #431)', () => {
|
||||||
|
const workflow = {
|
||||||
|
name: 'Test Workflow',
|
||||||
|
nodes: [],
|
||||||
|
connections: {},
|
||||||
|
settings: {
|
||||||
|
executionOrder: 'v1' as const, // Whitelisted
|
||||||
|
callerPolicy: 'workflowsFromSameOwner' as const, // Filtered out
|
||||||
|
timezone: 'America/New_York', // Whitelisted
|
||||||
|
someOtherProperty: 'value', // Filtered out
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const cleaned = cleanWorkflowForUpdate(workflow);
|
||||||
|
// Should keep only whitelisted properties
|
||||||
|
expect(cleaned.settings).toEqual({
|
||||||
|
executionOrder: 'v1',
|
||||||
|
timezone: 'America/New_York'
|
||||||
|
});
|
||||||
|
expect(cleaned.settings).not.toHaveProperty('callerPolicy');
|
||||||
|
expect(cleaned.settings).not.toHaveProperty('someOtherProperty');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1346,7 +1406,8 @@ describe('n8n-validation', () => {
|
|||||||
expect(forUpdate).not.toHaveProperty('active');
|
expect(forUpdate).not.toHaveProperty('active');
|
||||||
expect(forUpdate).not.toHaveProperty('tags');
|
expect(forUpdate).not.toHaveProperty('tags');
|
||||||
expect(forUpdate).not.toHaveProperty('meta');
|
expect(forUpdate).not.toHaveProperty('meta');
|
||||||
expect(forUpdate.settings).toEqual({}); // Settings replaced with empty object for API compatibility
|
// n8n API requires settings in updates, so minimal defaults (v1) are provided (Issue #431)
|
||||||
|
expect(forUpdate.settings).toEqual({ executionOrder: 'v1' });
|
||||||
expect(validateWorkflowStructure(forUpdate)).toEqual([]);
|
expect(validateWorkflowStructure(forUpdate)).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user