Skip to main content
Glama
apply-ops-integration.test.ts7.38 kB
import { describe, it, expect, jest, beforeEach } from '@jest/globals'; import { N8nClient } from '../n8n-client'; import { N8nConfig, N8nWorkflow, PatchOperation } from '../types'; // Mock axios jest.mock('axios'); describe('N8nClient applyOperations', () => { let client: N8nClient; let mockAxios: any; let sampleWorkflow: N8nWorkflow; beforeEach(() => { // Reset mocks jest.clearAllMocks(); // Create a proper mock axios instance const mockAxiosInstance = { get: jest.fn(), post: jest.fn(), put: jest.fn(), patch: jest.fn(), delete: jest.fn(), defaults: { headers: { common: {} } } }; // Mock axios create to return our mock instance mockAxios = mockAxiosInstance; // Setup axios mock const axios = require('axios'); axios.create = jest.fn().mockReturnValue(mockAxiosInstance); const config: N8nConfig = { baseUrl: 'http://localhost:5678', apiKey: 'test-key' }; client = new N8nClient(config); sampleWorkflow = { id: 1, name: 'Test Workflow', nodes: [ { id: 'webhook-1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 1, position: [100, 100], parameters: { httpMethod: 'GET', path: 'test' } } ], connections: {}, active: false, tags: ['test'] }; }); describe('applyOperations', () => { it('should apply operations successfully and update workflow', async () => { const operations: PatchOperation[] = [ { type: 'addNode', node: { id: 'set-1', name: 'Set', type: 'n8n-nodes-base.set', typeVersion: 1, position: [300, 100], parameters: { values: { string: [{ name: 'message', value: 'hello' }] } } } }, { type: 'setWorkflowProperty', property: 'active', value: true } ]; // Mock getWorkflow to return current workflow mockAxios.get.mockResolvedValueOnce({ data: { data: sampleWorkflow } }); // Mock updateWorkflow to return updated workflow const expectedUpdatedWorkflow = { ...sampleWorkflow, active: true, nodes: [ ...sampleWorkflow.nodes, (operations[0] as any).node ] }; mockAxios.put.mockResolvedValueOnce({ data: { data: expectedUpdatedWorkflow } }); const result = await client.applyOperations(1, operations); expect(result.success).toBe(true); expect(result.workflow).toBeDefined(); expect(result.workflow?.nodes).toHaveLength(2); expect(result.workflow?.active).toBe(true); // Verify API calls expect(mockAxios.get).toHaveBeenCalledWith('/workflows/1'); expect(mockAxios.put).toHaveBeenCalledWith('/workflows/1', expect.objectContaining({ active: true, nodes: expect.arrayContaining([ expect.objectContaining({ id: 'webhook-1' }), expect.objectContaining({ id: 'set-1' }) ]) }), expect.any(Object)); }); it('should return error when operation fails', async () => { const operations: PatchOperation[] = [ { type: 'addNode', node: { id: 'webhook-1', // Duplicate ID - should fail name: 'Another Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 1, position: [200, 200] } } ]; // Mock getWorkflow to return current workflow mockAxios.get.mockResolvedValueOnce({ data: { data: sampleWorkflow } }); const result = await client.applyOperations(1, operations); expect(result.success).toBe(false); expect(result.errors).toHaveLength(1); expect(result.errors![0].error).toContain('already exists'); expect(result.errors![0].operationIndex).toBe(0); // Verify that update was not called because operation failed expect(mockAxios.put).not.toHaveBeenCalled(); }); it('should handle version drift error from n8n API', async () => { const operations: PatchOperation[] = [ { type: 'setWorkflowProperty', property: 'name', value: 'Updated Name' } ]; // Mock getWorkflow to return current workflow mockAxios.get.mockResolvedValueOnce({ data: { data: sampleWorkflow } }); // Mock updateWorkflow to fail with version conflict const versionError = new Error('409 Conflict: Version drift detected'); mockAxios.put.mockRejectedValueOnce(versionError); const result = await client.applyOperations(1, operations); expect(result.success).toBe(false); expect(result.errors).toHaveLength(1); expect(result.errors![0].error).toContain('Version drift detected'); }); it('should handle workflow not found error', async () => { const operations: PatchOperation[] = [ { type: 'setWorkflowProperty', property: 'name', value: 'Updated Name' } ]; // Mock getWorkflow to fail const notFoundError = new Error('Workflow not found'); mockAxios.get.mockRejectedValueOnce(notFoundError); const result = await client.applyOperations(999, operations); expect(result.success).toBe(false); expect(result.errors).toHaveLength(1); expect(result.errors![0].error).toContain('Failed to retrieve workflow'); }); it('should handle empty operations array', async () => { const operations: PatchOperation[] = []; // Mock getWorkflow to return current workflow mockAxios.get.mockResolvedValueOnce({ data: { data: sampleWorkflow } }); // Mock updateWorkflow to return unchanged workflow mockAxios.put.mockResolvedValueOnce({ data: { data: sampleWorkflow } }); const result = await client.applyOperations(1, operations); expect(result.success).toBe(true); expect(result.workflow).toEqual(sampleWorkflow); }); it('should handle undefined operations input gracefully', async () => { // Mock getWorkflow to return current workflow mockAxios.get.mockResolvedValueOnce({ data: { data: sampleWorkflow, }, }); // Mock updateWorkflow to return unchanged workflow mockAxios.put.mockResolvedValueOnce({ data: { data: sampleWorkflow, }, }); // Pass undefined as operations (casting to any to simulate bad client input) const result = await client.applyOperations(1, undefined as any); expect(result.success).toBe(true); expect(result.workflow).toEqual(sampleWorkflow); // Verify API calls expect(mockAxios.get).toHaveBeenCalledWith('/workflows/1'); expect(mockAxios.put).toHaveBeenCalledWith( '/workflows/1', expect.objectContaining({ id: sampleWorkflow.id, name: sampleWorkflow.name }), expect.any(Object), ); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/get2knowio/n8n-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server