first commit
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

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,243 @@
import { mockDeep } from 'jest-mock-extended';
import type { IExecuteFunctions, ILoadOptionsFunctions } from 'n8n-workflow';
import { apiRequest } from './index';
describe('Ollama Transport', () => {
const executeFunctionsMock = mockDeep<IExecuteFunctions>();
const loadOptionsFunctionsMock = mockDeep<ILoadOptionsFunctions>();
beforeEach(() => {
jest.resetAllMocks();
});
describe('apiRequest', () => {
it('should make API request with basic auth', async () => {
executeFunctionsMock.getCredentials.mockResolvedValue({
baseUrl: 'http://localhost:11434',
apiKey: 'test-api-key',
});
executeFunctionsMock.helpers.httpRequestWithAuthentication.mockResolvedValue({
model: 'test-model',
response: 'test response',
});
const result = await apiRequest.call(executeFunctionsMock, 'POST', '/api/chat', {
body: { model: 'test-model', messages: [] },
});
expect(executeFunctionsMock.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith(
'ollamaApi',
{
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer test-api-key',
},
method: 'POST',
body: { model: 'test-model', messages: [] },
qs: undefined,
url: 'http://localhost:11434/api/chat',
json: true,
},
);
expect(result).toEqual({ model: 'test-model', response: 'test response' });
});
it('should make API request without auth when no API key provided', async () => {
executeFunctionsMock.getCredentials.mockResolvedValue({
baseUrl: 'http://localhost:11434',
});
executeFunctionsMock.helpers.httpRequestWithAuthentication.mockResolvedValue({
model: 'test-model',
response: 'test response',
});
await apiRequest.call(executeFunctionsMock, 'GET', '/api/tags');
expect(executeFunctionsMock.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith(
'ollamaApi',
{
headers: {
'Content-Type': 'application/json',
},
method: 'GET',
body: undefined,
qs: undefined,
url: 'http://localhost:11434/api/tags',
json: true,
},
);
});
it('should handle query parameters', async () => {
executeFunctionsMock.getCredentials.mockResolvedValue({
baseUrl: 'http://localhost:11434',
});
executeFunctionsMock.helpers.httpRequestWithAuthentication.mockResolvedValue([]);
await apiRequest.call(executeFunctionsMock, 'GET', '/api/tags', {
qs: { limit: 10, offset: 0 },
});
expect(executeFunctionsMock.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith(
'ollamaApi',
{
headers: {
'Content-Type': 'application/json',
},
method: 'GET',
body: undefined,
qs: { limit: 10, offset: 0 },
url: 'http://localhost:11434/api/tags',
json: true,
},
);
});
it('should handle custom headers', async () => {
executeFunctionsMock.getCredentials.mockResolvedValue({
baseUrl: 'http://localhost:11434',
apiKey: 'test-key',
});
executeFunctionsMock.helpers.httpRequestWithAuthentication.mockResolvedValue({});
await apiRequest.call(executeFunctionsMock, 'POST', '/api/generate', {
headers: { 'X-Custom-Header': 'custom-value' },
body: { model: 'test', prompt: 'hello' },
});
expect(executeFunctionsMock.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith(
'ollamaApi',
{
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer test-key',
'X-Custom-Header': 'custom-value',
},
method: 'POST',
body: { model: 'test', prompt: 'hello' },
qs: undefined,
url: 'http://localhost:11434/api/generate',
json: true,
},
);
});
it('should handle additional options', async () => {
executeFunctionsMock.getCredentials.mockResolvedValue({
baseUrl: 'http://localhost:11434',
});
executeFunctionsMock.helpers.httpRequestWithAuthentication.mockResolvedValue({});
await apiRequest.call(executeFunctionsMock, 'POST', '/api/chat', {
body: { model: 'test' },
option: { timeout: 30000, encoding: 'utf8' },
});
expect(executeFunctionsMock.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith(
'ollamaApi',
{
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
body: { model: 'test' },
qs: undefined,
url: 'http://localhost:11434/api/chat',
json: true,
timeout: 30000,
encoding: 'utf8',
},
);
});
it('should work with ILoadOptionsFunctions', async () => {
loadOptionsFunctionsMock.getCredentials.mockResolvedValue({
baseUrl: 'http://localhost:11434',
});
loadOptionsFunctionsMock.helpers.httpRequestWithAuthentication.mockResolvedValue({
models: [{ name: 'llama3.2:latest' }],
});
const result = await apiRequest.call(loadOptionsFunctionsMock, 'GET', '/api/tags');
expect(loadOptionsFunctionsMock.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith(
'ollamaApi',
{
headers: {
'Content-Type': 'application/json',
},
method: 'GET',
body: undefined,
qs: undefined,
url: 'http://localhost:11434/api/tags',
json: true,
},
);
expect(result).toEqual({ models: [{ name: 'llama3.2:latest' }] });
});
it('should handle baseUrl with trailing slash', async () => {
executeFunctionsMock.getCredentials.mockResolvedValue({
baseUrl: 'http://localhost:11434/',
apiKey: 'test-key',
});
executeFunctionsMock.helpers.httpRequestWithAuthentication.mockResolvedValue({});
await apiRequest.call(executeFunctionsMock, 'GET', '/api/tags');
expect(executeFunctionsMock.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith(
'ollamaApi',
expect.objectContaining({
url: 'http://localhost:11434/api/tags',
}),
);
});
it('should handle empty parameters object', async () => {
executeFunctionsMock.getCredentials.mockResolvedValue({
baseUrl: 'http://localhost:11434',
});
executeFunctionsMock.helpers.httpRequestWithAuthentication.mockResolvedValue({});
await apiRequest.call(executeFunctionsMock, 'GET', '/api/tags', {});
expect(executeFunctionsMock.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith(
'ollamaApi',
{
headers: {
'Content-Type': 'application/json',
},
method: 'GET',
body: undefined,
qs: undefined,
url: 'http://localhost:11434/api/tags',
json: true,
},
);
});
it('should handle undefined parameters', async () => {
executeFunctionsMock.getCredentials.mockResolvedValue({
baseUrl: 'http://localhost:11434',
});
executeFunctionsMock.helpers.httpRequestWithAuthentication.mockResolvedValue({});
await apiRequest.call(executeFunctionsMock, 'GET', '/api/tags');
expect(executeFunctionsMock.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith(
'ollamaApi',
{
headers: {
'Content-Type': 'application/json',
},
method: 'GET',
body: undefined,
qs: undefined,
url: 'http://localhost:11434/api/tags',
json: true,
},
);
});
});
});
@@ -0,0 +1,56 @@
import type {
IDataObject,
IExecuteFunctions,
IHttpRequestMethods,
ILoadOptionsFunctions,
} from 'n8n-workflow';
type RequestParameters = {
headers?: IDataObject;
body?: IDataObject | string;
qs?: IDataObject;
option?: IDataObject;
};
export async function apiRequest(
this: IExecuteFunctions | ILoadOptionsFunctions,
method: IHttpRequestMethods,
endpoint: string,
parameters?: RequestParameters,
) {
const { body, qs, option } = parameters ?? {};
const credentials = await this.getCredentials<{
apiKey?: string;
baseUrl: string;
}>('ollamaApi');
const apiKey = credentials.apiKey;
if (apiKey !== undefined && typeof apiKey !== 'string') {
throw new Error('API key must be a string');
}
const url = new URL(endpoint, credentials.baseUrl).toString();
const headers = parameters?.headers ?? {};
if (apiKey) {
headers.Authorization = `Bearer ${apiKey}`;
}
const options = {
headers: {
'Content-Type': 'application/json',
...headers,
},
method,
body,
qs,
url,
json: true,
};
if (option && Object.keys(option).length !== 0) {
Object.assign(options, option);
}
return await this.helpers.httpRequestWithAuthentication.call(this, 'ollamaApi', options);
}