fix: resolve multiple n8n_update_partial_workflow bugs (#635)

* fix: use correct MCP SDK API for server capabilities in test

getServerVersion() returns Implementation (name/version only), not the
full init result. Use client.getServerCapabilities() instead to access
server capabilities, fixing the CI typecheck failure.

Concieved by Romuald Członkowski - www.aiadvisors.pl/en

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve multiple n8n_update_partial_workflow bugs (#592, #599, #610, #623, #624, #625, #629, #630, #633)

Phase 1 - Data loss prevention:
- Add missing unary operators (empty, notEmpty, exists, notExists) to sanitizer (#592)
- Preserve positional empty arrays in connections during removeNode/cleanStale (#610)
- Scope sanitization to modified nodes only, preventing unrelated node corruption
- Add empty body {} to activate/deactivate POST calls to fix 415 errors (#633)

Phase 2 - Error handling & response clarity:
- Serialize Zod errors to readable "path: message" strings (#630)
- Add saved:true/false field to all response paths (#625)
- Improve updateNode error hint with correct structure example (#623)
- Track removed node names for better removeConnection errors (#624)

Phase 3 - Connection & type fixes:
- Coerce sourceOutput/targetInput to String() consistently (#629)
- Accept numeric sourceOutput/targetInput at Zod schema level via transform

Phase 4 - Tag operations via dedicated API (#599):
- Track tags as tagsToAdd/tagsToRemove instead of mutating workflow.tags
- Orchestrate tag creation and association via listTags/createTag/updateWorkflowTags
- Reconcile conflicting add/remove for same tag (last operation wins)
- Tag failures produce warnings, not hard errors

Conceived by Romuald Członkowski - www.aiadvisors.pl/en

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add v2.37.0 changelog entry

Conceived by Romuald Członkowski - www.aiadvisors.pl/en

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve pre-existing integration test failures in CI

- Create new MCP Server instance per connection in test helpers (SDK 1.27+
  requires separate Protocol instance per connection)
- Normalize database paths with path.resolve() in shared-database singleton
  to prevent path mismatch errors across test files
- Add no-op catch handler to deferred initialization promise in server.ts
  to prevent unhandled rejection warnings
- Properly call mcpServer.shutdown() in test helper close() to release
  shared database references

Conceived by Romuald Członkowski - www.aiadvisors.pl/en

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Romuald Członkowski
2026-03-14 17:27:33 +01:00
committed by GitHub
parent 248f859c49
commit 9590f751d2
15 changed files with 504 additions and 225 deletions

View File

@@ -424,7 +424,7 @@ describe('WorkflowDiffEngine', () => {
expect(result.success).toBe(false);
expect(result.errors![0].message).toContain('Missing required parameter \'updates\'');
expect(result.errors![0].message).toContain('Example:');
expect(result.errors![0].message).toContain('Correct structure:');
});
});
@@ -1898,16 +1898,15 @@ describe('WorkflowDiffEngine', () => {
};
const result = await diffEngine.applyDiff(baseWorkflow, request);
expect(result.success).toBe(true);
expect(result.workflow!.tags).toContain('production');
expect(result.workflow!.tags).toHaveLength(3);
expect(result.tagsToAdd).toContain('production');
});
it('should not add duplicate tags', async () => {
const operation: AddTagOperation = {
type: 'addTag',
tag: 'test' // Already exists
tag: 'test' // Already exists in workflow but tagsToAdd tracks it for API
};
const request: WorkflowDiffRequest = {
@@ -1916,9 +1915,10 @@ describe('WorkflowDiffEngine', () => {
};
const result = await diffEngine.applyDiff(baseWorkflow, request);
expect(result.success).toBe(true);
expect(result.workflow!.tags).toHaveLength(2); // No change
// Tags are now tracked for dedicated API call, not modified on workflow
expect(result.tagsToAdd).toEqual(['test']);
});
it('should create tags array if not exists', async () => {
@@ -1935,10 +1935,9 @@ describe('WorkflowDiffEngine', () => {
};
const result = await diffEngine.applyDiff(baseWorkflow, request);
expect(result.success).toBe(true);
expect(result.workflow!.tags).toBeDefined();
expect(result.workflow!.tags).toEqual(['new-tag']);
expect(result.tagsToAdd).toEqual(['new-tag']);
});
it('should remove an existing tag', async () => {
@@ -1953,10 +1952,9 @@ describe('WorkflowDiffEngine', () => {
};
const result = await diffEngine.applyDiff(baseWorkflow, request);
expect(result.success).toBe(true);
expect(result.workflow!.tags).not.toContain('test');
expect(result.workflow!.tags).toHaveLength(1);
expect(result.tagsToRemove).toContain('test');
});
it('should handle removing non-existent tag gracefully', async () => {
@@ -1971,9 +1969,11 @@ describe('WorkflowDiffEngine', () => {
};
const result = await diffEngine.applyDiff(baseWorkflow, request);
expect(result.success).toBe(true);
expect(result.workflow!.tags).toHaveLength(2); // No change
expect(result.tagsToRemove).toEqual(['non-existent']);
// workflow.tags unchanged since tags are now handled via dedicated API
expect(result.workflow!.tags).toHaveLength(2);
});
});
@@ -2509,7 +2509,7 @@ describe('WorkflowDiffEngine', () => {
expect(result.failed).toEqual([1]); // Operation 1 failed
expect(result.errors).toHaveLength(1);
expect(result.workflow.name).toBe('New Workflow Name');
expect(result.workflow.tags).toContain('production');
expect(result.tagsToAdd).toContain('production');
});
it('should return success false if all operations fail in continueOnError mode', async () => {
@@ -3356,7 +3356,7 @@ describe('WorkflowDiffEngine', () => {
expect(result.failed).toContain(1); // replaceConnections with invalid node
expect(result.applied).toContain(2); // removeConnection with ignoreErrors
expect(result.applied).toContain(3); // addTag
expect(result.workflow.tags).toContain('final-tag');
expect(result.tagsToAdd).toContain('final-tag');
});
});
@@ -4610,7 +4610,7 @@ describe('WorkflowDiffEngine', () => {
expect(result.success).toBe(true);
expect(result.operationsApplied).toBe(3);
expect(result.workflow!.name).toBe('Updated Workflow Name');
expect(result.workflow!.tags).toContain('production');
expect(result.tagsToAdd).toContain('production');
expect(result.shouldActivate).toBe(true);
});