395 lines
13 KiB
TypeScript
395 lines
13 KiB
TypeScript
/**
|
|
* N8nApiClient unit tests
|
|
*/
|
|
|
|
import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals'; // Explicit import
|
|
import axios, { AxiosInstance } from 'axios'; // Import AxiosInstance type
|
|
import { N8nApiClient } from '../../../src/api/client.js'; // Add .js
|
|
import { EnvConfig } from '../../../src/config/environment.js'; // Add .js
|
|
import { N8nApiError } from '../../../src/errors/index.js'; // Add .js
|
|
import { createMockAxiosInstance, createMockAxiosResponse } from '../../mocks/axios-mock.js'; // Add .js
|
|
import { mockApiResponses } from '../../mocks/n8n-fixtures.js'; // Add .js
|
|
|
|
// We will spy on axios.create instead of mocking the whole module
|
|
// jest.mock('axios');
|
|
// const mockedAxios = axios as jest.Mocked<typeof axios>;
|
|
|
|
|
|
describe('N8nApiClient', () => {
|
|
// Mock configuration
|
|
const mockConfig: EnvConfig = {
|
|
n8nApiUrl: 'https://n8n.example.com/api/v1',
|
|
n8nApiKey: 'test-api-key',
|
|
n8nWebhookUsername: 'test-user', // Added missing property
|
|
n8nWebhookPassword: 'test-password', // Added missing property
|
|
debug: false,
|
|
};
|
|
|
|
// Define a type for the mock axios instance based on axios-mock.ts
|
|
type MockAxiosInstance = ReturnType<typeof createMockAxiosInstance>;
|
|
|
|
// Mock axios instance
|
|
let mockAxios: MockAxiosInstance;
|
|
|
|
beforeEach(() => {
|
|
// Create the mock instance
|
|
mockAxios = createMockAxiosInstance();
|
|
// Spy on axios.create and mock its return value
|
|
jest.spyOn(axios, 'create').mockReturnValue(mockAxios as any);
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
mockAxios.reset();
|
|
});
|
|
|
|
describe('constructor', () => {
|
|
it('should create an axios instance with correct config', () => {
|
|
// Execute
|
|
new N8nApiClient(mockConfig);
|
|
|
|
// Assert
|
|
expect(axios.create).toHaveBeenCalledWith({ // Check the spy
|
|
baseURL: mockConfig.n8nApiUrl,
|
|
headers: {
|
|
'X-N8N-API-KEY': mockConfig.n8nApiKey,
|
|
'Accept': 'application/json',
|
|
},
|
|
timeout: 10000,
|
|
});
|
|
});
|
|
|
|
it('should set up debug interceptors when debug is true', () => {
|
|
// Setup
|
|
const debugConfig = { ...mockConfig, debug: true };
|
|
|
|
// Execute
|
|
new N8nApiClient(debugConfig);
|
|
|
|
// Assert
|
|
expect(mockAxios.interceptors.request.use).toHaveBeenCalled();
|
|
expect(mockAxios.interceptors.response.use).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not set up debug interceptors when debug is false', () => {
|
|
// Execute
|
|
new N8nApiClient(mockConfig);
|
|
|
|
// Assert
|
|
expect(mockAxios.interceptors.request.use).not.toHaveBeenCalled();
|
|
expect(mockAxios.interceptors.response.use).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('checkConnectivity', () => {
|
|
it('should resolve when connectivity check succeeds', async () => {
|
|
// Setup
|
|
const client = new N8nApiClient(mockConfig);
|
|
mockAxios.addMockResponse('get', '/workflows', {
|
|
status: 200,
|
|
statusText: 'OK', // Added statusText
|
|
data: { data: [] },
|
|
});
|
|
|
|
// Execute & Assert
|
|
await expect(client.checkConnectivity()).resolves.not.toThrow();
|
|
});
|
|
|
|
it('should throw an error when response status is not 200', async () => {
|
|
// Setup
|
|
const client = new N8nApiClient(mockConfig);
|
|
mockAxios.addMockResponse('get', '/workflows', {
|
|
status: 500,
|
|
statusText: 'Internal Server Error', // Added statusText
|
|
data: { message: 'Server error' },
|
|
});
|
|
|
|
// Execute & Assert
|
|
await expect(client.checkConnectivity()).rejects.toThrow(N8nApiError);
|
|
});
|
|
|
|
it('should throw an error when request fails', async () => {
|
|
// Setup
|
|
const client = new N8nApiClient(mockConfig);
|
|
mockAxios.addMockResponse('get', '/workflows', new Error('Network error'));
|
|
|
|
// Execute & Assert
|
|
await expect(client.checkConnectivity()).rejects.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('getWorkflows', () => {
|
|
it('should return workflows array on success', async () => {
|
|
// Setup
|
|
const client = new N8nApiClient(mockConfig);
|
|
const mockWorkflows = mockApiResponses.workflows.list;
|
|
mockAxios.addMockResponse('get', '/workflows', {
|
|
status: 200,
|
|
statusText: 'OK', // Added statusText
|
|
data: mockWorkflows,
|
|
});
|
|
|
|
// Execute
|
|
const result = await client.getWorkflows();
|
|
|
|
// Assert
|
|
expect(result).toEqual(mockWorkflows.data);
|
|
expect(mockAxios.get).toHaveBeenCalledWith('/workflows');
|
|
});
|
|
|
|
it('should handle empty response', async () => {
|
|
// Setup
|
|
const client = new N8nApiClient(mockConfig);
|
|
mockAxios.addMockResponse('get', '/workflows', {
|
|
status: 200,
|
|
statusText: 'OK', // Added statusText
|
|
data: {},
|
|
});
|
|
|
|
// Execute
|
|
const result = await client.getWorkflows();
|
|
|
|
// Assert
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it('should throw an error when request fails', async () => {
|
|
// Setup
|
|
const client = new N8nApiClient(mockConfig);
|
|
mockAxios.addMockResponse('get', '/workflows', new Error('Network error'));
|
|
|
|
// Execute & Assert
|
|
await expect(client.getWorkflows()).rejects.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('getWorkflow', () => {
|
|
it('should return a workflow on success', async () => {
|
|
// Setup
|
|
const client = new N8nApiClient(mockConfig);
|
|
const workflowId = 'test-workflow-1';
|
|
const mockWorkflow = mockApiResponses.workflows.single(workflowId);
|
|
mockAxios.addMockResponse('get', `/workflows/${workflowId}`, {
|
|
status: 200,
|
|
statusText: 'OK', // Added statusText
|
|
data: mockWorkflow,
|
|
});
|
|
|
|
// Execute
|
|
const result = await client.getWorkflow(workflowId);
|
|
|
|
// Assert
|
|
expect(result).toEqual(mockWorkflow);
|
|
expect(mockAxios.get).toHaveBeenCalledWith(`/workflows/${workflowId}`);
|
|
});
|
|
|
|
it('should throw an error when request fails', async () => {
|
|
// Setup
|
|
const client = new N8nApiClient(mockConfig);
|
|
const workflowId = 'test-workflow-1';
|
|
mockAxios.addMockResponse('get', `/workflows/${workflowId}`, new Error('Network error'));
|
|
|
|
// Execute & Assert
|
|
await expect(client.getWorkflow(workflowId)).rejects.toThrow();
|
|
});
|
|
});
|
|
|
|
// Additional tests for other API client methods
|
|
describe('executeWorkflow', () => {
|
|
it('should execute a workflow successfully', async () => {
|
|
// Setup
|
|
const client = new N8nApiClient(mockConfig);
|
|
const workflowId = 'test-workflow-1';
|
|
const mockData = { inputs: { value: 'test' } };
|
|
const mockResponse = { executionId: 'exec-123', success: true };
|
|
|
|
mockAxios.addMockResponse('post', `/workflows/${workflowId}/execute`, {
|
|
status: 200,
|
|
statusText: 'OK', // Added statusText
|
|
data: mockResponse,
|
|
});
|
|
|
|
// Execute
|
|
const result = await client.executeWorkflow(workflowId, mockData);
|
|
|
|
// Assert
|
|
expect(result).toEqual(mockResponse);
|
|
expect(mockAxios.post).toHaveBeenCalledWith(`/workflows/${workflowId}/execute`, mockData);
|
|
});
|
|
});
|
|
|
|
describe('createWorkflow', () => {
|
|
it('should create a workflow successfully', async () => {
|
|
// Setup
|
|
const client = new N8nApiClient(mockConfig);
|
|
const newWorkflow = { name: 'New Workflow', nodes: [], connections: {} };
|
|
const mockResponse = mockApiResponses.workflows.create(newWorkflow);
|
|
|
|
mockAxios.addMockResponse('post', '/workflows', {
|
|
status: 200,
|
|
statusText: 'OK', // Added statusText
|
|
data: mockResponse,
|
|
});
|
|
|
|
// Execute
|
|
const result = await client.createWorkflow(newWorkflow);
|
|
|
|
// Assert
|
|
expect(result).toEqual(mockResponse);
|
|
expect(mockAxios.post).toHaveBeenCalledWith('/workflows', newWorkflow);
|
|
});
|
|
});
|
|
|
|
describe('updateWorkflow', () => {
|
|
it('should update a workflow successfully', async () => {
|
|
// Setup
|
|
const client = new N8nApiClient(mockConfig);
|
|
const workflowId = 'test-workflow-1';
|
|
const updatedWorkflow = { name: 'Updated Workflow', nodes: [], connections: {} };
|
|
const mockResponse = mockApiResponses.workflows.update(workflowId, updatedWorkflow);
|
|
|
|
mockAxios.addMockResponse('put', `/workflows/${workflowId}`, {
|
|
status: 200,
|
|
statusText: 'OK', // Added statusText
|
|
data: mockResponse,
|
|
});
|
|
|
|
// Execute
|
|
const result = await client.updateWorkflow(workflowId, updatedWorkflow);
|
|
|
|
// Assert
|
|
expect(result).toEqual(mockResponse);
|
|
expect(mockAxios.put).toHaveBeenCalledWith(`/workflows/${workflowId}`, updatedWorkflow);
|
|
});
|
|
});
|
|
|
|
describe('deleteWorkflow', () => {
|
|
it('should delete a workflow successfully', async () => {
|
|
// Setup
|
|
const client = new N8nApiClient(mockConfig);
|
|
const workflowId = 'test-workflow-1';
|
|
const mockResponse = mockApiResponses.workflows.delete;
|
|
|
|
mockAxios.addMockResponse('delete', `/workflows/${workflowId}`, {
|
|
status: 200,
|
|
statusText: 'OK', // Added statusText
|
|
data: mockResponse,
|
|
});
|
|
|
|
// Execute
|
|
const result = await client.deleteWorkflow(workflowId);
|
|
|
|
// Assert
|
|
expect(result).toEqual(mockResponse);
|
|
expect(mockAxios.delete).toHaveBeenCalledWith(`/workflows/${workflowId}`);
|
|
});
|
|
});
|
|
|
|
describe('activateWorkflow', () => {
|
|
it('should activate a workflow successfully', async () => {
|
|
// Setup
|
|
const client = new N8nApiClient(mockConfig);
|
|
const workflowId = 'test-workflow-1';
|
|
const mockResponse = mockApiResponses.workflows.activate(workflowId);
|
|
|
|
mockAxios.addMockResponse('post', `/workflows/${workflowId}/activate`, {
|
|
status: 200,
|
|
statusText: 'OK', // Added statusText
|
|
data: mockResponse,
|
|
});
|
|
|
|
// Execute
|
|
const result = await client.activateWorkflow(workflowId);
|
|
|
|
// Assert
|
|
expect(result).toEqual(mockResponse);
|
|
expect(mockAxios.post).toHaveBeenCalledWith(`/workflows/${workflowId}/activate`);
|
|
});
|
|
});
|
|
|
|
describe('deactivateWorkflow', () => {
|
|
it('should deactivate a workflow successfully', async () => {
|
|
// Setup
|
|
const client = new N8nApiClient(mockConfig);
|
|
const workflowId = 'test-workflow-1';
|
|
const mockResponse = mockApiResponses.workflows.deactivate(workflowId);
|
|
|
|
mockAxios.addMockResponse('post', `/workflows/${workflowId}/deactivate`, {
|
|
status: 200,
|
|
statusText: 'OK', // Added statusText
|
|
data: mockResponse,
|
|
});
|
|
|
|
// Execute
|
|
const result = await client.deactivateWorkflow(workflowId);
|
|
|
|
// Assert
|
|
expect(result).toEqual(mockResponse);
|
|
expect(mockAxios.post).toHaveBeenCalledWith(`/workflows/${workflowId}/deactivate`);
|
|
});
|
|
});
|
|
|
|
describe('getExecutions', () => {
|
|
it('should return executions array on success', async () => {
|
|
// Setup
|
|
const client = new N8nApiClient(mockConfig);
|
|
const mockExecutions = mockApiResponses.executions.list;
|
|
mockAxios.addMockResponse('get', '/executions', {
|
|
status: 200,
|
|
statusText: 'OK', // Added statusText
|
|
data: mockExecutions,
|
|
});
|
|
|
|
// Execute
|
|
const result = await client.getExecutions();
|
|
|
|
// Assert
|
|
expect(result).toEqual(mockExecutions.data);
|
|
expect(mockAxios.get).toHaveBeenCalledWith('/executions');
|
|
});
|
|
});
|
|
|
|
describe('getExecution', () => {
|
|
it('should return an execution on success', async () => {
|
|
// Setup
|
|
const client = new N8nApiClient(mockConfig);
|
|
const executionId = 'test-execution-1';
|
|
const mockExecution = mockApiResponses.executions.single(executionId);
|
|
mockAxios.addMockResponse('get', `/executions/${executionId}`, {
|
|
status: 200,
|
|
statusText: 'OK', // Added statusText
|
|
data: mockExecution,
|
|
});
|
|
|
|
// Execute
|
|
const result = await client.getExecution(executionId);
|
|
|
|
// Assert
|
|
expect(result).toEqual(mockExecution);
|
|
expect(mockAxios.get).toHaveBeenCalledWith(`/executions/${executionId}`);
|
|
});
|
|
});
|
|
|
|
describe('deleteExecution', () => {
|
|
it('should delete an execution successfully', async () => {
|
|
// Setup
|
|
const client = new N8nApiClient(mockConfig);
|
|
const executionId = 'test-execution-1';
|
|
const mockResponse = mockApiResponses.executions.delete;
|
|
|
|
mockAxios.addMockResponse('delete', `/executions/${executionId}`, {
|
|
status: 200,
|
|
statusText: 'OK', // Added statusText
|
|
data: mockResponse,
|
|
});
|
|
|
|
// Execute
|
|
const result = await client.deleteExecution(executionId);
|
|
|
|
// Assert
|
|
expect(result).toEqual(mockResponse);
|
|
expect(mockAxios.delete).toHaveBeenCalledWith(`/executions/${executionId}`);
|
|
});
|
|
});
|
|
});
|