Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
227 lines
7.8 KiB
TypeScript
227 lines
7.8 KiB
TypeScript
import { mockDeep } from 'jest-mock-extended';
|
|
import { NodeOperationError, type IExecuteFunctions, type INode } from 'n8n-workflow';
|
|
|
|
import * as image from './image';
|
|
import * as text from './text';
|
|
import { router } from './router';
|
|
|
|
jest.mock('./image');
|
|
jest.mock('./text');
|
|
|
|
describe('Ollama Router', () => {
|
|
const executeFunctionsMock = mockDeep<IExecuteFunctions>();
|
|
const mockImageExecute = jest.fn();
|
|
const mockTextExecute = jest.fn();
|
|
|
|
beforeEach(() => {
|
|
jest.resetAllMocks();
|
|
(image as any).analyze = { execute: mockImageExecute };
|
|
(text as any).message = { execute: mockTextExecute };
|
|
|
|
executeFunctionsMock.getInputData.mockReturnValue([
|
|
{ json: { input: 'test1' } },
|
|
{ json: { input: 'test2' } },
|
|
]);
|
|
});
|
|
|
|
describe('router', () => {
|
|
it('should route to text.message operation', async () => {
|
|
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
|
if (parameter === 'resource') return 'text';
|
|
if (parameter === 'operation') return 'message';
|
|
return undefined;
|
|
});
|
|
|
|
mockTextExecute.mockResolvedValueOnce([
|
|
{ json: { result: 'response1' }, pairedItem: { item: 0 } },
|
|
]);
|
|
mockTextExecute.mockResolvedValueOnce([
|
|
{ json: { result: 'response2' }, pairedItem: { item: 1 } },
|
|
]);
|
|
|
|
const result = await router.call(executeFunctionsMock);
|
|
|
|
expect(result).toEqual([
|
|
[
|
|
{ json: { result: 'response1' }, pairedItem: { item: 0 } },
|
|
{ json: { result: 'response2' }, pairedItem: { item: 1 } },
|
|
],
|
|
]);
|
|
expect(mockTextExecute).toHaveBeenCalledTimes(2);
|
|
expect(mockTextExecute).toHaveBeenNthCalledWith(1, 0);
|
|
expect(mockTextExecute).toHaveBeenNthCalledWith(2, 1);
|
|
});
|
|
|
|
it('should route to image.analyze operation', async () => {
|
|
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
|
if (parameter === 'resource') return 'image';
|
|
if (parameter === 'operation') return 'analyze';
|
|
return undefined;
|
|
});
|
|
|
|
mockImageExecute.mockResolvedValueOnce([
|
|
{ json: { result: 'image analysis 1' }, pairedItem: { item: 0 } },
|
|
]);
|
|
mockImageExecute.mockResolvedValueOnce([
|
|
{ json: { result: 'image analysis 2' }, pairedItem: { item: 1 } },
|
|
]);
|
|
|
|
const result = await router.call(executeFunctionsMock);
|
|
|
|
expect(result).toEqual([
|
|
[
|
|
{ json: { result: 'image analysis 1' }, pairedItem: { item: 0 } },
|
|
{ json: { result: 'image analysis 2' }, pairedItem: { item: 1 } },
|
|
],
|
|
]);
|
|
expect(mockImageExecute).toHaveBeenCalledTimes(2);
|
|
expect(mockImageExecute).toHaveBeenNthCalledWith(1, 0);
|
|
expect(mockImageExecute).toHaveBeenNthCalledWith(2, 1);
|
|
});
|
|
|
|
it('should throw error for unsupported resource', async () => {
|
|
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
|
if (parameter === 'resource') return 'unsupported';
|
|
if (parameter === 'operation') return 'test';
|
|
return undefined;
|
|
});
|
|
|
|
const mockNode = { name: 'Ollama', type: 'n8n-nodes-langchain.ollama' } as INode;
|
|
executeFunctionsMock.getNode.mockReturnValue(mockNode);
|
|
|
|
await expect(router.call(executeFunctionsMock)).rejects.toThrow(NodeOperationError);
|
|
await expect(router.call(executeFunctionsMock)).rejects.toThrow(
|
|
'The resource "unsupported" is not supported!',
|
|
);
|
|
});
|
|
|
|
it('should handle execution errors with continueOnFail enabled', async () => {
|
|
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
|
if (parameter === 'resource') return 'text';
|
|
if (parameter === 'operation') return 'message';
|
|
return undefined;
|
|
});
|
|
|
|
executeFunctionsMock.continueOnFail.mockReturnValue(true);
|
|
mockTextExecute.mockResolvedValueOnce([
|
|
{ json: { result: 'success' }, pairedItem: { item: 0 } },
|
|
]);
|
|
mockTextExecute.mockRejectedValueOnce(new Error('API Error'));
|
|
|
|
const result = await router.call(executeFunctionsMock);
|
|
|
|
expect(result).toEqual([
|
|
[
|
|
{ json: { result: 'success' }, pairedItem: { item: 0 } },
|
|
{ json: { error: 'API Error' }, pairedItem: { item: 1 } },
|
|
],
|
|
]);
|
|
expect(mockTextExecute).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('should throw NodeOperationError when continueOnFail is disabled', async () => {
|
|
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
|
if (parameter === 'resource') return 'text';
|
|
if (parameter === 'operation') return 'message';
|
|
return undefined;
|
|
});
|
|
|
|
executeFunctionsMock.continueOnFail.mockReturnValue(false);
|
|
const mockNode = { name: 'Ollama', type: 'n8n-nodes-langchain.ollama' } as INode;
|
|
executeFunctionsMock.getNode.mockReturnValue(mockNode);
|
|
|
|
const originalError = new Error('API Connection Failed');
|
|
mockTextExecute.mockRejectedValueOnce(originalError);
|
|
|
|
await expect(router.call(executeFunctionsMock)).rejects.toThrow(NodeOperationError);
|
|
});
|
|
|
|
it('should process multiple items and accumulate results', async () => {
|
|
executeFunctionsMock.getInputData.mockReturnValue([
|
|
{ json: { input: 'test1' } },
|
|
{ json: { input: 'test2' } },
|
|
{ json: { input: 'test3' } },
|
|
]);
|
|
|
|
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
|
if (parameter === 'resource') return 'text';
|
|
if (parameter === 'operation') return 'message';
|
|
return undefined;
|
|
});
|
|
|
|
mockTextExecute.mockResolvedValueOnce([
|
|
{ json: { result: 'response1' }, pairedItem: { item: 0 } },
|
|
]);
|
|
mockTextExecute.mockResolvedValueOnce([
|
|
{ json: { result: 'response2a' }, pairedItem: { item: 1 } },
|
|
{ json: { result: 'response2b' }, pairedItem: { item: 1 } },
|
|
]);
|
|
mockTextExecute.mockResolvedValueOnce([
|
|
{ json: { result: 'response3' }, pairedItem: { item: 2 } },
|
|
]);
|
|
|
|
const result = await router.call(executeFunctionsMock);
|
|
|
|
expect(result).toEqual([
|
|
[
|
|
{ json: { result: 'response1' }, pairedItem: { item: 0 } },
|
|
{ json: { result: 'response2a' }, pairedItem: { item: 1 } },
|
|
{ json: { result: 'response2b' }, pairedItem: { item: 1 } },
|
|
{ json: { result: 'response3' }, pairedItem: { item: 2 } },
|
|
],
|
|
]);
|
|
expect(mockTextExecute).toHaveBeenCalledTimes(3);
|
|
expect(mockTextExecute).toHaveBeenNthCalledWith(1, 0);
|
|
expect(mockTextExecute).toHaveBeenNthCalledWith(2, 1);
|
|
expect(mockTextExecute).toHaveBeenNthCalledWith(3, 2);
|
|
});
|
|
|
|
it('should handle empty input data', async () => {
|
|
executeFunctionsMock.getInputData.mockReturnValue([]);
|
|
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
|
if (parameter === 'resource') return 'text';
|
|
if (parameter === 'operation') return 'message';
|
|
return undefined;
|
|
});
|
|
|
|
const result = await router.call(executeFunctionsMock);
|
|
|
|
expect(result).toEqual([[]]);
|
|
expect(mockTextExecute).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should handle mixed success and failure with continueOnFail', async () => {
|
|
executeFunctionsMock.getInputData.mockReturnValue([
|
|
{ json: { input: 'test1' } },
|
|
{ json: { input: 'test2' } },
|
|
{ json: { input: 'test3' } },
|
|
]);
|
|
|
|
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
|
if (parameter === 'resource') return 'text';
|
|
if (parameter === 'operation') return 'message';
|
|
return undefined;
|
|
});
|
|
|
|
executeFunctionsMock.continueOnFail.mockReturnValue(true);
|
|
mockTextExecute.mockResolvedValueOnce([
|
|
{ json: { result: 'success1' }, pairedItem: { item: 0 } },
|
|
]);
|
|
mockTextExecute.mockRejectedValueOnce(new Error('Error in item 2'));
|
|
mockTextExecute.mockResolvedValueOnce([
|
|
{ json: { result: 'success3' }, pairedItem: { item: 2 } },
|
|
]);
|
|
|
|
const result = await router.call(executeFunctionsMock);
|
|
|
|
expect(result).toEqual([
|
|
[
|
|
{ json: { result: 'success1' }, pairedItem: { item: 0 } },
|
|
{ json: { error: 'Error in item 2' }, pairedItem: { item: 1 } },
|
|
{ json: { result: 'success3' }, pairedItem: { item: 2 } },
|
|
],
|
|
]);
|
|
});
|
|
});
|
|
});
|