Files
n8n-enterprise-unlocked/packages/cli/src/events/__tests__/log-streaming-event-relay.test.ts

1448 lines
36 KiB
TypeScript

import { GLOBAL_OWNER_ROLE, type IWorkflowDb } from '@n8n/db';
import { mock } from 'jest-mock-extended';
import type { InstanceSettings } from 'n8n-core';
import type { INode, IRun, IWorkflowBase } from 'n8n-workflow';
import type { MessageEventBus } from '@/eventbus/message-event-bus/message-event-bus';
import { EventService } from '@/events/event.service';
import type { RelayEventMap } from '@/events/maps/relay.event-map';
import { LogStreamingEventRelay } from '@/events/relays/log-streaming.event-relay';
describe('LogStreamingEventRelay', () => {
const eventBus = mock<MessageEventBus>();
const eventService = new EventService();
const hostId = 'host-xyz';
const instanceSettings = mock<InstanceSettings>({ hostId });
new LogStreamingEventRelay(eventService, eventBus, instanceSettings).init();
afterEach(() => {
jest.clearAllMocks();
});
describe('workflow events', () => {
it('should log on `workflow-created` event', () => {
const event: RelayEventMap['workflow-created'] = {
user: {
id: '123',
email: 'john@n8n.io',
firstName: 'John',
lastName: 'Doe',
role: { slug: 'owner' },
},
workflow: mock<IWorkflowBase>({
id: 'wf123',
name: 'Test Workflow',
}),
publicApi: false,
projectId: 'proj123',
projectType: 'personal',
};
eventService.emit('workflow-created', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.workflow.created',
payload: {
userId: '123',
_email: 'john@n8n.io',
_firstName: 'John',
_lastName: 'Doe',
globalRole: 'owner',
workflowId: 'wf123',
workflowName: 'Test Workflow',
},
});
});
it('should log on `workflow-archived` event', () => {
const event: RelayEventMap['workflow-archived'] = {
user: {
id: '456',
email: 'jane@n8n.io',
firstName: 'Jane',
lastName: 'Smith',
role: { slug: 'user' },
},
workflowId: 'wf789',
publicApi: false,
};
eventService.emit('workflow-archived', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.workflow.archived',
payload: {
userId: '456',
_email: 'jane@n8n.io',
_firstName: 'Jane',
_lastName: 'Smith',
globalRole: 'user',
workflowId: 'wf789',
},
});
});
it('should log on `workflow-unarchived` event', () => {
const event: RelayEventMap['workflow-unarchived'] = {
user: {
id: '456',
email: 'jane@n8n.io',
firstName: 'Jane',
lastName: 'Smith',
role: { slug: 'user' },
},
workflowId: 'wf789',
publicApi: false,
};
eventService.emit('workflow-unarchived', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.workflow.unarchived',
payload: {
userId: '456',
_email: 'jane@n8n.io',
_firstName: 'Jane',
_lastName: 'Smith',
globalRole: 'user',
workflowId: 'wf789',
},
});
});
it('should log on `workflow-deleted` event', () => {
const event: RelayEventMap['workflow-deleted'] = {
user: {
id: '456',
email: 'jane@n8n.io',
firstName: 'Jane',
lastName: 'Smith',
role: { slug: 'user' },
},
workflowId: 'wf789',
publicApi: false,
};
eventService.emit('workflow-deleted', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.workflow.deleted',
payload: {
userId: '456',
_email: 'jane@n8n.io',
_firstName: 'Jane',
_lastName: 'Smith',
globalRole: 'user',
workflowId: 'wf789',
},
});
});
it('should log on `workflow-saved` event', () => {
const event: RelayEventMap['workflow-saved'] = {
user: {
id: '789',
email: 'alex@n8n.io',
firstName: 'Alex',
lastName: 'Johnson',
role: { slug: 'editor' },
},
workflow: mock<IWorkflowDb>({ id: 'wf101', name: 'Updated Workflow' }),
publicApi: false,
};
eventService.emit('workflow-saved', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.workflow.updated',
payload: {
userId: '789',
_email: 'alex@n8n.io',
_firstName: 'Alex',
_lastName: 'Johnson',
globalRole: 'editor',
workflowId: 'wf101',
workflowName: 'Updated Workflow',
},
});
});
it('should log on `workflow-pre-execute` event', () => {
const workflow = mock<IWorkflowBase>({
id: 'wf202',
name: 'Test Workflow',
active: true,
nodes: [],
connections: {},
staticData: undefined,
settings: {},
});
const event: RelayEventMap['workflow-pre-execute'] = {
executionId: 'exec123',
data: workflow,
};
eventService.emit('workflow-pre-execute', event);
expect(eventBus.sendWorkflowEvent).toHaveBeenCalledWith({
eventName: 'n8n.workflow.started',
payload: {
executionId: 'exec123',
userId: undefined,
workflowId: 'wf202',
isManual: false,
workflowName: 'Test Workflow',
},
});
});
it('should log on `workflow-post-execute` for successful execution', () => {
const payload = mock<RelayEventMap['workflow-post-execute']>({
executionId: 'some-id',
userId: 'some-id',
workflow: mock<IWorkflowBase>({ id: 'some-id', name: 'some-name' }),
runData: mock<IRun>({
finished: true,
status: 'success',
mode: 'manual',
data: { resultData: {} },
}),
});
eventService.emit('workflow-post-execute', payload);
const { runData: _, workflow: __, ...rest } = payload;
expect(eventBus.sendWorkflowEvent).toHaveBeenCalledWith({
eventName: 'n8n.workflow.success',
payload: {
...rest,
success: true, // same as finished
isManual: true,
workflowName: 'some-name',
workflowId: 'some-id',
},
});
});
it('should log job completion on `workflow-post-execute` for successful job', () => {
const runData = mock<IRun>({
finished: true,
status: 'success',
mode: 'manual',
jobId: '12345',
data: { resultData: {} },
});
const event = {
executionId: 'exec-123',
userId: 'user-456',
workflow: mock<IWorkflowBase>({ id: 'wf-789', name: 'Test Workflow' }),
runData,
};
eventService.emit('workflow-post-execute', event);
expect(eventBus.sendQueueEvent).toHaveBeenCalledWith({
eventName: 'n8n.queue.job.completed',
payload: {
executionId: 'exec-123',
workflowId: 'wf-789',
hostId: 'host-xyz',
jobId: '12345',
},
});
});
it('should log on `workflow-post-execute` event for failed execution', () => {
const runData = mock<IRun>({
status: 'error',
mode: 'manual',
finished: false,
data: {
resultData: {
lastNodeExecuted: 'some-node',
// @ts-expect-error Partial mock
error: {
node: mock<INode>({ type: 'some-type' }),
message: 'some-message',
},
errorMessage: 'some-message',
},
},
}) as unknown as IRun;
const event = {
executionId: 'some-id',
userId: 'some-id',
workflow: mock<IWorkflowBase>({ id: 'some-id', name: 'some-name' }),
runData,
};
eventService.emit('workflow-post-execute', event);
const { runData: _, workflow: __, ...rest } = event;
expect(eventBus.sendWorkflowEvent).toHaveBeenCalledWith({
eventName: 'n8n.workflow.failed',
payload: {
...rest,
success: false, // same as finished
isManual: true,
workflowName: 'some-name',
workflowId: 'some-id',
lastNodeExecuted: 'some-node',
errorNodeType: 'some-type',
errorMessage: 'some-message',
},
});
});
it('should log job failure on `workflow-post-execute` for failed job', () => {
const runData = mock<IRun>({
finished: false,
status: 'error',
mode: 'manual',
jobId: '67890',
data: {
resultData: {
lastNodeExecuted: 'some-node',
// @ts-expect-error Partial mock
error: {
node: mock<INode>({ type: 'some-type' }),
message: 'some-message',
},
errorMessage: 'some-message',
},
},
}) as unknown as IRun;
const event = {
executionId: 'exec-456',
userId: 'user-789',
workflow: mock<IWorkflowBase>({ id: 'wf-101', name: 'Failed Workflow' }),
runData,
};
eventService.emit('workflow-post-execute', event);
expect(eventBus.sendQueueEvent).toHaveBeenCalledWith({
eventName: 'n8n.queue.job.failed',
payload: {
executionId: 'exec-456',
workflowId: 'wf-101',
hostId: 'host-xyz',
jobId: '67890',
},
});
});
});
describe('user events', () => {
it('should log on `user-updated` event', () => {
const event: RelayEventMap['user-updated'] = {
user: {
id: 'user456',
email: 'updated@example.com',
firstName: 'Updated',
lastName: 'User',
role: { slug: 'global:member' },
},
fieldsChanged: ['firstName', 'lastName', 'password'],
};
eventService.emit('user-updated', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.user.updated',
payload: {
userId: 'user456',
_email: 'updated@example.com',
_firstName: 'Updated',
_lastName: 'User',
globalRole: 'global:member',
fieldsChanged: ['firstName', 'lastName', 'password'],
},
});
});
it('should log on `user-deleted` event', () => {
const event: RelayEventMap['user-deleted'] = {
user: {
id: '123',
email: 'john@n8n.io',
firstName: 'John',
lastName: 'Doe',
role: { slug: 'some-role' },
},
targetUserOldStatus: 'active',
publicApi: false,
migrationStrategy: 'transfer_data',
targetUserId: '456',
migrationUserId: '789',
};
eventService.emit('user-deleted', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.user.deleted',
payload: {
userId: '123',
_email: 'john@n8n.io',
_firstName: 'John',
_lastName: 'Doe',
globalRole: 'some-role',
},
});
});
it('should log on `user-invited` event', () => {
const event: RelayEventMap['user-invited'] = {
user: {
id: 'user101',
email: 'inviter@example.com',
firstName: 'Inviter',
lastName: 'User',
role: { slug: GLOBAL_OWNER_ROLE.slug },
},
targetUserId: ['newUser123'],
publicApi: false,
emailSent: true,
inviteeRole: 'global:member',
};
eventService.emit('user-invited', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.user.invited',
payload: {
userId: 'user101',
_email: 'inviter@example.com',
_firstName: 'Inviter',
_lastName: 'User',
globalRole: 'global:owner',
targetUserId: ['newUser123'],
},
});
});
it('should log on `user-reinvited` event', () => {
const event: RelayEventMap['user-reinvited'] = {
user: {
id: 'user202',
email: 'reinviter@example.com',
firstName: 'Reinviter',
lastName: 'User',
role: { slug: 'global:admin' },
},
targetUserId: ['existingUser456'],
};
eventService.emit('user-reinvited', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.user.reinvited',
payload: {
userId: 'user202',
_email: 'reinviter@example.com',
_firstName: 'Reinviter',
_lastName: 'User',
globalRole: 'global:admin',
targetUserId: ['existingUser456'],
},
});
});
it('should log on `user-signed-up` event', () => {
const event: RelayEventMap['user-signed-up'] = {
user: {
id: 'user303',
email: 'newuser@example.com',
firstName: 'New',
lastName: 'User',
role: { slug: 'global:member' },
},
userType: 'email',
wasDisabledLdapUser: false,
};
eventService.emit('user-signed-up', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.user.signedup',
payload: {
userId: 'user303',
_email: 'newuser@example.com',
_firstName: 'New',
_lastName: 'User',
globalRole: 'global:member',
},
});
});
it('should log on `user-logged-in` event', () => {
const event: RelayEventMap['user-logged-in'] = {
user: {
id: 'user404',
email: 'loggedin@example.com',
firstName: 'Logged',
lastName: 'In',
role: { slug: GLOBAL_OWNER_ROLE.slug },
},
authenticationMethod: 'email',
};
eventService.emit('user-logged-in', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.user.login.success',
payload: {
userId: 'user404',
_email: 'loggedin@example.com',
_firstName: 'Logged',
_lastName: 'In',
globalRole: 'global:owner',
authenticationMethod: 'email',
},
});
});
});
describe('click events', () => {
it('should log on `user-password-reset-request-click` event', () => {
const event: RelayEventMap['user-password-reset-request-click'] = {
user: {
id: 'user101',
email: 'user101@example.com',
firstName: 'John',
lastName: 'Doe',
role: { slug: 'global:member' },
},
};
eventService.emit('user-password-reset-request-click', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.user.reset.requested',
payload: {
userId: 'user101',
_email: 'user101@example.com',
_firstName: 'John',
_lastName: 'Doe',
globalRole: 'global:member',
},
});
});
it('should log on `user-invite-email-click` event', () => {
const event: RelayEventMap['user-invite-email-click'] = {
inviter: {
id: '123',
email: 'john@n8n.io',
firstName: 'John',
lastName: 'Doe',
role: { slug: 'some-role' },
},
invitee: {
id: '456',
email: 'jane@n8n.io',
firstName: 'Jane',
lastName: 'Doe',
role: { slug: 'some-other-role' },
},
};
eventService.emit('user-invite-email-click', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.user.invitation.accepted',
payload: {
inviter: {
userId: '123',
_email: 'john@n8n.io',
_firstName: 'John',
_lastName: 'Doe',
globalRole: 'some-role',
},
invitee: {
userId: '456',
_email: 'jane@n8n.io',
_firstName: 'Jane',
_lastName: 'Doe',
globalRole: 'some-other-role',
},
},
});
});
it('should log on `user-password-reset-email-click` event', () => {
const event: RelayEventMap['user-password-reset-email-click'] = {
user: {
id: 'user505',
email: 'resetuser@example.com',
firstName: 'Reset',
lastName: 'User',
role: { slug: 'global:member' },
},
};
eventService.emit('user-password-reset-email-click', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.user.reset',
payload: {
userId: 'user505',
_email: 'resetuser@example.com',
_firstName: 'Reset',
_lastName: 'User',
globalRole: 'global:member',
},
});
});
});
describe('node events', () => {
it('should log on `node-pre-execute` event', () => {
const workflow = mock<IWorkflowBase>({
id: 'wf303',
name: 'Test Workflow with Nodes',
active: true,
nodes: [
{
id: 'node1',
name: 'Start Node',
type: 'n8n-nodes-base.start',
typeVersion: 1,
position: [100, 200],
},
{
id: 'node2',
name: 'HTTP Request',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 1,
position: [300, 200],
},
],
connections: {},
settings: {},
});
const event: RelayEventMap['node-pre-execute'] = {
executionId: 'exec456',
nodeName: 'HTTP Request',
workflow,
nodeId: 'node2',
nodeType: 'n8n-nodes-base.httpRequest',
};
eventService.emit('node-pre-execute', event);
expect(eventBus.sendNodeEvent).toHaveBeenCalledWith({
eventName: 'n8n.node.started',
payload: {
executionId: 'exec456',
nodeName: 'HTTP Request',
workflowId: 'wf303',
workflowName: 'Test Workflow with Nodes',
nodeType: 'n8n-nodes-base.httpRequest',
nodeId: 'node2',
},
});
});
it('should log on `node-post-execute` event', () => {
const workflow = mock<IWorkflowBase>({
id: 'wf404',
name: 'Test Workflow with Completed Node',
active: true,
nodes: [
{
id: 'node1',
name: 'Start Node',
type: 'n8n-nodes-base.start',
typeVersion: 1,
position: [100, 200],
},
{
id: 'node2',
name: 'HTTP Response',
type: 'n8n-nodes-base.httpResponse',
typeVersion: 1,
position: [300, 200],
},
],
connections: {},
settings: {},
});
const event: RelayEventMap['node-post-execute'] = {
executionId: 'exec789',
nodeName: 'HTTP Response',
workflow,
nodeId: 'node2',
nodeType: 'n8n-nodes-base.httpResponse',
};
eventService.emit('node-post-execute', event);
expect(eventBus.sendNodeEvent).toHaveBeenCalledWith({
eventName: 'n8n.node.finished',
payload: {
executionId: 'exec789',
nodeName: 'HTTP Response',
workflowId: 'wf404',
workflowName: 'Test Workflow with Completed Node',
nodeType: 'n8n-nodes-base.httpResponse',
nodeId: 'node2',
},
});
});
});
describe('credentials events', () => {
it('should log on `credentials-shared` event', () => {
const event: RelayEventMap['credentials-shared'] = {
user: {
id: 'user123',
email: 'sharer@example.com',
firstName: 'Alice',
lastName: 'Sharer',
role: { slug: GLOBAL_OWNER_ROLE.slug },
},
credentialId: 'cred789',
credentialType: 'githubApi',
userIdSharer: 'user123',
userIdsShareesAdded: ['user456', 'user789'],
shareesRemoved: null,
};
eventService.emit('credentials-shared', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.user.credentials.shared',
payload: {
userId: 'user123',
_email: 'sharer@example.com',
_firstName: 'Alice',
_lastName: 'Sharer',
globalRole: 'global:owner',
credentialId: 'cred789',
credentialType: 'githubApi',
userIdSharer: 'user123',
userIdsShareesAdded: ['user456', 'user789'],
shareesRemoved: null,
},
});
});
it('should log on `credentials-created` event', () => {
const event: RelayEventMap['credentials-created'] = {
user: {
id: 'user123',
email: 'user@example.com',
firstName: 'Test',
lastName: 'User',
role: { slug: GLOBAL_OWNER_ROLE.slug },
},
credentialType: 'githubApi',
credentialId: 'cred456',
publicApi: false,
projectId: 'proj789',
projectType: 'Personal',
};
eventService.emit('credentials-created', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.user.credentials.created',
payload: {
userId: 'user123',
_email: 'user@example.com',
_firstName: 'Test',
_lastName: 'User',
globalRole: 'global:owner',
credentialType: 'githubApi',
credentialId: 'cred456',
publicApi: false,
projectId: 'proj789',
projectType: 'Personal',
},
});
});
it('should log on `credentials-deleted` event', () => {
const event: RelayEventMap['credentials-deleted'] = {
user: {
id: 'user707',
email: 'creduser@example.com',
firstName: 'Cred',
lastName: 'User',
role: { slug: GLOBAL_OWNER_ROLE.slug },
},
credentialId: 'cred789',
credentialType: 'githubApi',
};
eventService.emit('credentials-deleted', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.user.credentials.deleted',
payload: {
userId: 'user707',
_email: 'creduser@example.com',
_firstName: 'Cred',
_lastName: 'User',
globalRole: 'global:owner',
credentialId: 'cred789',
credentialType: 'githubApi',
},
});
});
it('should log on `credentials-updated` event', () => {
const event: RelayEventMap['credentials-updated'] = {
user: {
id: 'user808',
email: 'updatecred@example.com',
firstName: 'Update',
lastName: 'Cred',
role: { slug: GLOBAL_OWNER_ROLE.slug },
},
credentialId: 'cred101',
credentialType: 'slackApi',
};
eventService.emit('credentials-updated', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.user.credentials.updated',
payload: {
userId: 'user808',
_email: 'updatecred@example.com',
_firstName: 'Update',
_lastName: 'Cred',
globalRole: 'global:owner',
credentialId: 'cred101',
credentialType: 'slackApi',
},
});
});
});
describe('auth events', () => {
it('should log on `user-login-failed` event', () => {
const event: RelayEventMap['user-login-failed'] = {
userEmail: 'user@example.com',
authenticationMethod: 'email',
reason: 'Invalid password',
};
eventService.emit('user-login-failed', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.user.login.failed',
payload: {
userEmail: 'user@example.com',
authenticationMethod: 'email',
reason: 'Invalid password',
},
});
});
});
describe('community package events', () => {
it('should log on `community-package-updated` event', () => {
const event: RelayEventMap['community-package-updated'] = {
user: {
id: 'user202',
email: 'packageupdater@example.com',
firstName: 'Package',
lastName: 'Updater',
role: { slug: 'global:admin' },
},
packageName: 'n8n-nodes-awesome-package',
packageVersionCurrent: '1.0.0',
packageVersionNew: '1.1.0',
packageNodeNames: ['AwesomeNode1', 'AwesomeNode2'],
packageAuthor: 'Jane Doe',
packageAuthorEmail: 'jane@example.com',
};
eventService.emit('community-package-updated', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.package.updated',
payload: {
userId: 'user202',
_email: 'packageupdater@example.com',
_firstName: 'Package',
_lastName: 'Updater',
globalRole: 'global:admin',
packageName: 'n8n-nodes-awesome-package',
packageVersionCurrent: '1.0.0',
packageVersionNew: '1.1.0',
packageNodeNames: ['AwesomeNode1', 'AwesomeNode2'],
packageAuthor: 'Jane Doe',
packageAuthorEmail: 'jane@example.com',
},
});
});
it('should log on `community-package-installed` event', () => {
const event: RelayEventMap['community-package-installed'] = {
user: {
id: 'user789',
email: 'admin@example.com',
firstName: 'Admin',
lastName: 'User',
role: { slug: 'global:admin' },
},
inputString: 'n8n-nodes-custom-package',
packageName: 'n8n-nodes-custom-package',
success: true,
packageVersion: '1.0.0',
packageNodeNames: ['CustomNode1', 'CustomNode2'],
packageAuthor: 'John Doe',
packageAuthorEmail: 'john@example.com',
};
eventService.emit('community-package-installed', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.package.installed',
payload: {
userId: 'user789',
_email: 'admin@example.com',
_firstName: 'Admin',
_lastName: 'User',
globalRole: 'global:admin',
inputString: 'n8n-nodes-custom-package',
packageName: 'n8n-nodes-custom-package',
success: true,
packageVersion: '1.0.0',
packageNodeNames: ['CustomNode1', 'CustomNode2'],
packageAuthor: 'John Doe',
packageAuthorEmail: 'john@example.com',
},
});
});
it('should log on `community-package-deleted` event', () => {
const event: RelayEventMap['community-package-deleted'] = {
user: {
id: 'user909',
email: 'packagedeleter@example.com',
firstName: 'Package',
lastName: 'Deleter',
role: { slug: 'global:admin' },
},
packageName: 'n8n-nodes-awesome-package',
packageVersion: '1.0.0',
packageNodeNames: ['AwesomeNode1', 'AwesomeNode2'],
packageAuthor: 'John Doe',
packageAuthorEmail: 'john@example.com',
};
eventService.emit('community-package-deleted', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.package.deleted',
payload: {
userId: 'user909',
_email: 'packagedeleter@example.com',
_firstName: 'Package',
_lastName: 'Deleter',
globalRole: 'global:admin',
packageName: 'n8n-nodes-awesome-package',
packageVersion: '1.0.0',
packageNodeNames: ['AwesomeNode1', 'AwesomeNode2'],
packageAuthor: 'John Doe',
packageAuthorEmail: 'john@example.com',
},
});
});
});
describe('email events', () => {
it('should log on `email-failed` event', () => {
const event: RelayEventMap['email-failed'] = {
user: {
id: 'user789',
email: 'recipient@example.com',
firstName: 'Failed',
lastName: 'Recipient',
role: { slug: 'global:member' },
},
messageType: 'New user invite',
publicApi: false,
};
eventService.emit('email-failed', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.user.email.failed',
payload: {
userId: 'user789',
_email: 'recipient@example.com',
_firstName: 'Failed',
_lastName: 'Recipient',
globalRole: 'global:member',
messageType: 'New user invite',
},
});
});
});
describe('public API events', () => {
it('should log on `public-api-key-created` event', () => {
const event: RelayEventMap['public-api-key-created'] = {
user: {
id: 'user101',
email: 'apiuser@example.com',
firstName: 'API',
lastName: 'User',
role: { slug: GLOBAL_OWNER_ROLE.slug },
},
publicApi: true,
};
eventService.emit('public-api-key-created', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.user.api.created',
payload: {
userId: 'user101',
_email: 'apiuser@example.com',
_firstName: 'API',
_lastName: 'User',
globalRole: 'global:owner',
},
});
});
it('should log on `public-api-key-deleted` event', () => {
const event: RelayEventMap['public-api-key-deleted'] = {
user: {
id: 'user606',
email: 'apiuser@example.com',
firstName: 'API',
lastName: 'User',
role: { slug: GLOBAL_OWNER_ROLE.slug },
},
publicApi: true,
};
eventService.emit('public-api-key-deleted', event);
expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({
eventName: 'n8n.audit.user.api.deleted',
payload: {
userId: 'user606',
_email: 'apiuser@example.com',
_firstName: 'API',
_lastName: 'User',
globalRole: 'global:owner',
},
});
});
});
describe('execution events', () => {
it('should log on `execution-started-during-bootup` event', () => {
const event: RelayEventMap['execution-started-during-bootup'] = {
executionId: 'exec101010',
};
eventService.emit('execution-started-during-bootup', event);
expect(eventBus.sendExecutionEvent).toHaveBeenCalledWith({
eventName: 'n8n.execution.started-during-bootup',
payload: {
executionId: 'exec101010',
},
});
});
it('should log on `execution-throttled` event', () => {
const event: RelayEventMap['execution-throttled'] = {
executionId: 'exec123456',
type: 'production',
};
eventService.emit('execution-throttled', event);
expect(eventBus.sendExecutionEvent).toHaveBeenCalledWith({
eventName: 'n8n.execution.throttled',
payload: {
executionId: 'exec123456',
type: 'production',
},
});
});
});
describe('AI events', () => {
it('should log on `ai-messages-retrieved-from-memory` event', () => {
const payload: RelayEventMap['ai-messages-retrieved-from-memory'] = {
msg: 'Hello, world!',
executionId: 'exec789',
nodeName: 'Memory',
workflowId: 'wf123',
workflowName: 'My Workflow',
nodeType: 'n8n-nodes-base.memory',
};
eventService.emit('ai-messages-retrieved-from-memory', payload);
expect(eventBus.sendAiNodeEvent).toHaveBeenCalledWith({
eventName: 'n8n.ai.memory.get.messages',
payload,
});
});
it('should log on `ai-message-added-to-memory` event', () => {
const payload: RelayEventMap['ai-message-added-to-memory'] = {
msg: 'Test',
executionId: 'exec456',
nodeName: 'Memory',
workflowId: 'wf789',
workflowName: 'My Workflow',
nodeType: 'n8n-nodes-base.memory',
};
eventService.emit('ai-message-added-to-memory', payload);
expect(eventBus.sendAiNodeEvent).toHaveBeenCalledWith({
eventName: 'n8n.ai.memory.added.message',
payload,
});
});
it('should log on `ai-output-parsed` event', () => {
const payload: RelayEventMap['ai-output-parsed'] = {
msg: 'Test',
executionId: 'exec123',
nodeName: 'Output Parser',
workflowId: 'wf456',
workflowName: 'My Workflow',
nodeType: 'n8n-nodes-base.outputParser',
};
eventService.emit('ai-output-parsed', payload);
expect(eventBus.sendAiNodeEvent).toHaveBeenCalledWith({
eventName: 'n8n.ai.output.parser.parsed',
payload,
});
});
it('should log on `ai-documents-retrieved` event', () => {
const payload: RelayEventMap['ai-documents-retrieved'] = {
msg: 'Test',
executionId: 'exec789',
nodeName: 'Retriever',
workflowId: 'wf123',
workflowName: 'My Workflow',
nodeType: 'n8n-nodes-base.retriever',
};
eventService.emit('ai-documents-retrieved', payload);
expect(eventBus.sendAiNodeEvent).toHaveBeenCalledWith({
eventName: 'n8n.ai.retriever.get.relevant.documents',
payload,
});
});
it('should log on `ai-document-embedded` event', () => {
const payload: RelayEventMap['ai-document-embedded'] = {
msg: 'Test',
executionId: 'exec456',
nodeName: 'Embeddings',
workflowId: 'wf789',
workflowName: 'My Workflow',
nodeType: 'n8n-nodes-base.embeddings',
};
eventService.emit('ai-document-embedded', payload);
expect(eventBus.sendAiNodeEvent).toHaveBeenCalledWith({
eventName: 'n8n.ai.embeddings.embedded.document',
payload,
});
});
it('should log on `ai-query-embedded` event', () => {
const payload: RelayEventMap['ai-query-embedded'] = {
msg: 'Test',
executionId: 'exec123',
nodeName: 'Embeddings',
workflowId: 'wf456',
workflowName: 'My Workflow',
nodeType: 'n8n-nodes-base.embeddings',
};
eventService.emit('ai-query-embedded', payload);
expect(eventBus.sendAiNodeEvent).toHaveBeenCalledWith({
eventName: 'n8n.ai.embeddings.embedded.query',
payload,
});
});
it('should log on `ai-document-processed` event', () => {
const payload: RelayEventMap['ai-document-processed'] = {
msg: 'Test',
executionId: 'exec789',
nodeName: 'Embeddings',
workflowId: 'wf789',
workflowName: 'My Workflow',
nodeType: 'n8n-nodes-base.embeddings',
};
eventService.emit('ai-document-processed', payload);
expect(eventBus.sendAiNodeEvent).toHaveBeenCalledWith({
eventName: 'n8n.ai.document.processed',
payload,
});
});
it('should log on `ai-text-split` event', () => {
const payload: RelayEventMap['ai-text-split'] = {
msg: 'Test',
executionId: 'exec456',
nodeName: 'Text Splitter',
workflowId: 'wf789',
workflowName: 'My Workflow',
nodeType: 'n8n-nodes-base.textSplitter',
};
eventService.emit('ai-text-split', payload);
expect(eventBus.sendAiNodeEvent).toHaveBeenCalledWith({
eventName: 'n8n.ai.text.splitter.split',
payload,
});
});
it('should log on `ai-tool-called` event', () => {
const payload: RelayEventMap['ai-tool-called'] = {
msg: 'Test',
executionId: 'exec123',
nodeName: 'Tool',
workflowId: 'wf456',
workflowName: 'My Workflow',
nodeType: 'n8n-nodes-base.tool',
};
eventService.emit('ai-tool-called', payload);
expect(eventBus.sendAiNodeEvent).toHaveBeenCalledWith({
eventName: 'n8n.ai.tool.called',
payload,
});
});
it('should log on `ai-vector-store-searched` event', () => {
const payload: RelayEventMap['ai-vector-store-searched'] = {
msg: 'Test',
executionId: 'exec789',
nodeName: 'Vector Store',
workflowId: 'wf123',
workflowName: 'My Workflow',
nodeType: 'n8n-nodes-base.vectorStore',
};
eventService.emit('ai-vector-store-searched', payload);
expect(eventBus.sendAiNodeEvent).toHaveBeenCalledWith({
eventName: 'n8n.ai.vector.store.searched',
payload,
});
});
it('should log on `ai-llm-generated-output` event', () => {
const payload: RelayEventMap['ai-llm-generated-output'] = {
msg: 'Test',
executionId: 'exec456',
nodeName: 'OpenAI',
workflowId: 'wf789',
workflowName: 'My Workflow',
nodeType: 'n8n-nodes-base.openai',
};
eventService.emit('ai-llm-generated-output', payload);
expect(eventBus.sendAiNodeEvent).toHaveBeenCalledWith({
eventName: 'n8n.ai.llm.generated',
payload,
});
});
it('should log on `ai-llm-errored` event', () => {
const payload: RelayEventMap['ai-llm-errored'] = {
msg: 'Test',
executionId: 'exec789',
nodeName: 'OpenAI',
workflowId: 'wf123',
workflowName: 'My Workflow',
nodeType: 'n8n-nodes-base.openai',
};
eventService.emit('ai-llm-errored', payload);
expect(eventBus.sendAiNodeEvent).toHaveBeenCalledWith({
eventName: 'n8n.ai.llm.error',
payload,
});
});
it('should log on `ai-vector-store-populated` event', () => {
const payload: RelayEventMap['ai-vector-store-populated'] = {
msg: 'Test',
executionId: 'exec456',
nodeName: 'Vector Store',
workflowId: 'wf789',
workflowName: 'My Workflow',
nodeType: 'n8n-nodes-base.vectorStore',
};
eventService.emit('ai-vector-store-populated', payload);
expect(eventBus.sendAiNodeEvent).toHaveBeenCalledWith({
eventName: 'n8n.ai.vector.store.populated',
payload,
});
});
it('should log on `ai-vector-store-updated` event', () => {
const payload: RelayEventMap['ai-vector-store-updated'] = {
msg: 'Test',
executionId: 'exec789',
nodeName: 'Vector Store',
workflowId: 'wf123',
workflowName: 'My Workflow',
nodeType: 'n8n-nodes-base.vectorStore',
};
eventService.emit('ai-vector-store-updated', payload);
expect(eventBus.sendAiNodeEvent).toHaveBeenCalledWith({
eventName: 'n8n.ai.vector.store.updated',
payload,
});
});
});
describe('runner events', () => {
it('should log on `runner-task-requested` event', () => {
const event: RelayEventMap['runner-task-requested'] = {
taskId: 't-1',
nodeId: 'n-2',
executionId: 'e-3',
workflowId: 'w-4',
};
eventService.emit('runner-task-requested', event);
expect(eventBus.sendRunnerEvent).toHaveBeenCalledWith({
eventName: 'n8n.runner.task.requested',
payload: {
taskId: 't-1',
nodeId: 'n-2',
executionId: 'e-3',
workflowId: 'w-4',
},
});
});
it('should log on `runner-response-received` event', () => {
const event: RelayEventMap['runner-response-received'] = {
taskId: 't-1',
nodeId: 'n-2',
executionId: 'e-3',
workflowId: 'w-4',
};
eventService.emit('runner-response-received', event);
expect(eventBus.sendRunnerEvent).toHaveBeenCalledWith({
eventName: 'n8n.runner.response.received',
payload: {
taskId: 't-1',
nodeId: 'n-2',
executionId: 'e-3',
workflowId: 'w-4',
},
});
});
});
describe('job events', () => {
it('should log on `job-enqueued` event', () => {
const event: RelayEventMap['job-enqueued'] = {
executionId: 'exec-1',
workflowId: 'wf-2',
hostId,
jobId: 'job-4',
};
eventService.emit('job-enqueued', event);
expect(eventBus.sendQueueEvent).toHaveBeenCalledWith({
eventName: 'n8n.queue.job.enqueued',
payload: {
executionId: 'exec-1',
workflowId: 'wf-2',
hostId,
jobId: 'job-4',
},
});
});
it('should log on `job-dequeued` event', () => {
const event: RelayEventMap['job-dequeued'] = {
executionId: 'exec-1',
workflowId: 'wf-2',
hostId,
jobId: 'job-4',
};
eventService.emit('job-dequeued', event);
expect(eventBus.sendQueueEvent).toHaveBeenCalledWith({
eventName: 'n8n.queue.job.dequeued',
payload: {
executionId: 'exec-1',
workflowId: 'wf-2',
hostId,
jobId: 'job-4',
},
});
});
it('should log on `job-stalled` event', () => {
const event: RelayEventMap['job-stalled'] = {
executionId: 'exec-1',
workflowId: 'wf-2',
hostId,
jobId: 'job-4',
};
eventService.emit('job-stalled', event);
expect(eventBus.sendQueueEvent).toHaveBeenCalledWith({
eventName: 'n8n.queue.job.stalled',
payload: {
executionId: 'exec-1',
workflowId: 'wf-2',
hostId,
jobId: 'job-4',
},
});
});
});
});