feat: Add n8n-benchmark cli (no-changelog) (#10410)
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { N8nApiClient } from './n8nApiClient';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
|
||||
export class AuthenticatedN8nApiClient extends N8nApiClient {
|
||||
constructor(
|
||||
apiBaseUrl: string,
|
||||
private readonly authCookie: string,
|
||||
) {
|
||||
super(apiBaseUrl);
|
||||
}
|
||||
|
||||
static async createUsingUsernameAndPassword(
|
||||
apiClient: N8nApiClient,
|
||||
loginDetails: {
|
||||
email: string;
|
||||
password: string;
|
||||
},
|
||||
) {
|
||||
const response = await apiClient.restApiRequest('/login', {
|
||||
method: 'POST',
|
||||
data: loginDetails,
|
||||
});
|
||||
|
||||
const cookieHeader = response.headers['set-cookie'];
|
||||
const authCookie = Array.isArray(cookieHeader) ? cookieHeader.join('; ') : cookieHeader;
|
||||
assert(authCookie);
|
||||
|
||||
return new AuthenticatedN8nApiClient(apiClient.apiBaseUrl, authCookie);
|
||||
}
|
||||
|
||||
async get<T>(endpoint: string) {
|
||||
return await this.authenticatedRequest<T>(endpoint, {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
async post<T>(endpoint: string, data: unknown) {
|
||||
return await this.authenticatedRequest<T>(endpoint, {
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
async patch<T>(endpoint: string, data: unknown) {
|
||||
return await this.authenticatedRequest<T>(endpoint, {
|
||||
method: 'PATCH',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
async delete<T>(endpoint: string) {
|
||||
return await this.authenticatedRequest<T>(endpoint, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
protected async authenticatedRequest<T>(endpoint: string, init: Omit<AxiosRequestConfig, 'url'>) {
|
||||
return await this.restApiRequest<T>(endpoint, {
|
||||
...init,
|
||||
headers: {
|
||||
...init.headers,
|
||||
cookie: this.authCookie,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
78
packages/@n8n/benchmark/src/n8nApiClient/n8nApiClient.ts
Normal file
78
packages/@n8n/benchmark/src/n8nApiClient/n8nApiClient.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import axios, { AxiosError, AxiosRequestConfig } from 'axios';
|
||||
|
||||
export class N8nApiClient {
|
||||
constructor(public readonly apiBaseUrl: string) {}
|
||||
|
||||
async waitForInstanceToBecomeOnline(): Promise<void> {
|
||||
const HEALTH_ENDPOINT = 'healthz';
|
||||
const START_TIME = Date.now();
|
||||
const INTERVAL_MS = 1000;
|
||||
const TIMEOUT_MS = 60_000;
|
||||
|
||||
while (Date.now() - START_TIME < TIMEOUT_MS) {
|
||||
try {
|
||||
const response = await axios.request({
|
||||
url: `${this.apiBaseUrl}/${HEALTH_ENDPOINT}`,
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
if (response.status === 200 && response.data.status === 'ok') {
|
||||
return;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
console.log(`n8n instance not online yet, retrying in ${INTERVAL_MS / 1000} seconds...`);
|
||||
await this.delay(INTERVAL_MS);
|
||||
}
|
||||
|
||||
throw new Error(`n8n instance did not come online within ${TIMEOUT_MS / 1000} seconds`);
|
||||
}
|
||||
|
||||
async setupOwnerIfNeeded(loginDetails: { email: string; password: string }) {
|
||||
const response = await this.restApiRequest<{ message: string }>('/owner/setup', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
email: loginDetails.email,
|
||||
password: loginDetails.password,
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
},
|
||||
// Don't throw on non-2xx responses
|
||||
validateStatus: () => true,
|
||||
});
|
||||
|
||||
const responsePayload = response.data;
|
||||
|
||||
if (response.status === 200) {
|
||||
console.log('Owner setup successful');
|
||||
} else if (response.status === 400) {
|
||||
if (responsePayload.message === 'Instance owner already setup')
|
||||
console.log('Owner already set up');
|
||||
} else {
|
||||
throw new Error(
|
||||
`Owner setup failed with status ${response.status}: ${responsePayload.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async restApiRequest<T>(endpoint: string, init: Omit<AxiosRequestConfig, 'url'>) {
|
||||
try {
|
||||
return await axios.request<T>({
|
||||
...init,
|
||||
url: this.getRestEndpointUrl(endpoint),
|
||||
});
|
||||
} catch (e) {
|
||||
const error = e as AxiosError;
|
||||
console.error(`[ERROR] Request failed ${init.method} ${endpoint}`, error?.response?.data);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
protected getRestEndpointUrl(endpoint: string) {
|
||||
return `${this.apiBaseUrl}/rest${endpoint}`;
|
||||
}
|
||||
|
||||
private delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* n8n workflow. This is a simplified version of the actual workflow object.
|
||||
*/
|
||||
export type Workflow = {
|
||||
id: string;
|
||||
name: string;
|
||||
tags?: string[];
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Workflow } from '@/n8nApiClient/n8nApiClient.types';
|
||||
import { AuthenticatedN8nApiClient } from './authenticatedN8nApiClient';
|
||||
|
||||
export class WorkflowApiClient {
|
||||
constructor(private readonly apiClient: AuthenticatedN8nApiClient) {}
|
||||
|
||||
async getAllWorkflows(): Promise<Workflow[]> {
|
||||
const response = await this.apiClient.get<{ count: number; data: Workflow[] }>('/workflows');
|
||||
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
async createWorkflow(workflow: unknown): Promise<Workflow> {
|
||||
const response = await this.apiClient.post<{ data: Workflow }>('/workflows', workflow);
|
||||
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
async activateWorkflow(workflow: Workflow): Promise<Workflow> {
|
||||
const response = await this.apiClient.patch<{ data: Workflow }>(`/workflows/${workflow.id}`, {
|
||||
...workflow,
|
||||
active: true,
|
||||
});
|
||||
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
async deleteWorkflow(workflowId: Workflow['id']): Promise<void> {
|
||||
await this.apiClient.delete(`/workflows/${workflowId}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user