2023-05-02 01:37:19 -07:00
|
|
|
import type express from 'express';
|
2023-01-04 00:47:48 -08:00
|
|
|
import config from '@/config';
|
|
|
|
import axios from 'axios';
|
|
|
|
import syslog from 'syslog-client';
|
2023-03-17 09:24:05 -07:00
|
|
|
import { v4 as uuid } from 'uuid';
|
2023-04-12 01:59:14 -07:00
|
|
|
import { Container } from 'typedi';
|
2023-03-17 09:24:05 -07:00
|
|
|
import type { SuperAgentTest } from 'supertest';
|
2023-01-04 00:47:48 -08:00
|
|
|
import * as utils from './shared/utils';
|
|
|
|
import * as testDb from './shared/testDb';
|
2023-05-02 01:37:19 -07:00
|
|
|
import type { Role } from '@db/entities/Role';
|
|
|
|
import type { User } from '@db/entities/User';
|
|
|
|
import type {
|
|
|
|
MessageEventBusDestinationSentryOptions,
|
|
|
|
MessageEventBusDestinationSyslogOptions,
|
|
|
|
MessageEventBusDestinationWebhookOptions,
|
|
|
|
} from 'n8n-workflow';
|
2023-01-04 00:47:48 -08:00
|
|
|
import {
|
|
|
|
defaultMessageEventBusDestinationSentryOptions,
|
|
|
|
defaultMessageEventBusDestinationSyslogOptions,
|
|
|
|
defaultMessageEventBusDestinationWebhookOptions,
|
|
|
|
} from 'n8n-workflow';
|
2023-03-30 00:59:04 -07:00
|
|
|
import { eventBus } from '@/eventbus';
|
2023-01-13 09:12:22 -08:00
|
|
|
import { EventMessageGeneric } from '@/eventbus/EventMessageClasses/EventMessageGeneric';
|
2023-05-02 01:37:19 -07:00
|
|
|
import type { MessageEventBusDestinationSyslog } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationSyslog.ee';
|
|
|
|
import type { MessageEventBusDestinationWebhook } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationWebhook.ee';
|
|
|
|
import type { MessageEventBusDestinationSentry } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationSentry.ee';
|
2023-01-13 09:12:22 -08:00
|
|
|
import { EventMessageAudit } from '@/eventbus/EventMessageClasses/EventMessageAudit';
|
2023-05-02 01:37:19 -07:00
|
|
|
import type { EventNamesTypes } from '@/eventbus/EventMessageClasses';
|
2023-03-31 04:51:38 -07:00
|
|
|
import { License } from '@/License';
|
2023-01-04 00:47:48 -08:00
|
|
|
|
|
|
|
jest.unmock('@/eventbus/MessageEventBus/MessageEventBus');
|
|
|
|
jest.mock('axios');
|
|
|
|
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
|
|
|
jest.mock('syslog-client');
|
|
|
|
const mockedSyslog = syslog as jest.Mocked<typeof syslog>;
|
|
|
|
|
|
|
|
let app: express.Application;
|
|
|
|
let globalOwnerRole: Role;
|
|
|
|
let owner: User;
|
|
|
|
let unAuthOwnerAgent: SuperAgentTest;
|
|
|
|
let authOwnerAgent: SuperAgentTest;
|
|
|
|
|
|
|
|
const testSyslogDestination: MessageEventBusDestinationSyslogOptions = {
|
|
|
|
...defaultMessageEventBusDestinationSyslogOptions,
|
|
|
|
id: 'b88038f4-0a89-4e94-89a9-658dfdb74539',
|
|
|
|
protocol: 'udp',
|
|
|
|
label: 'Test Syslog',
|
|
|
|
enabled: false,
|
|
|
|
subscribedEvents: ['n8n.test.message', 'n8n.audit.user.updated'],
|
|
|
|
};
|
|
|
|
|
|
|
|
const testWebhookDestination: MessageEventBusDestinationWebhookOptions = {
|
|
|
|
...defaultMessageEventBusDestinationWebhookOptions,
|
|
|
|
id: '88be6560-bfb4-455c-8aa1-06971e9e5522',
|
|
|
|
url: 'http://localhost:3456',
|
2023-05-02 01:37:19 -07:00
|
|
|
method: 'POST',
|
2023-01-04 00:47:48 -08:00
|
|
|
label: 'Test Webhook',
|
|
|
|
enabled: false,
|
|
|
|
subscribedEvents: ['n8n.test.message', 'n8n.audit.user.updated'],
|
|
|
|
};
|
2023-03-17 09:24:05 -07:00
|
|
|
|
2023-01-04 00:47:48 -08:00
|
|
|
const testSentryDestination: MessageEventBusDestinationSentryOptions = {
|
|
|
|
...defaultMessageEventBusDestinationSentryOptions,
|
|
|
|
id: '450ca04b-87dd-4837-a052-ab3a347a00e9',
|
|
|
|
dsn: 'http://localhost:3000',
|
|
|
|
label: 'Test Sentry',
|
|
|
|
enabled: false,
|
|
|
|
subscribedEvents: ['n8n.test.message', 'n8n.audit.user.updated'],
|
|
|
|
};
|
|
|
|
|
2023-01-13 06:39:25 -08:00
|
|
|
async function confirmIdInAll(id: string) {
|
|
|
|
const sent = await eventBus.getEventsAll();
|
|
|
|
expect(sent.length).toBeGreaterThan(0);
|
|
|
|
expect(sent.find((msg) => msg.id === id)).toBeTruthy();
|
2023-01-04 00:47:48 -08:00
|
|
|
}
|
|
|
|
|
2023-01-13 06:39:25 -08:00
|
|
|
async function confirmIdSent(id: string) {
|
|
|
|
const sent = await eventBus.getEventsSent();
|
|
|
|
expect(sent.length).toBeGreaterThan(0);
|
|
|
|
expect(sent.find((msg) => msg.id === id)).toBeTruthy();
|
|
|
|
}
|
2023-01-04 00:47:48 -08:00
|
|
|
|
|
|
|
beforeAll(async () => {
|
2023-03-31 04:51:38 -07:00
|
|
|
Container.get(License).isLogStreamingEnabled = () => true;
|
2023-01-27 02:19:47 -08:00
|
|
|
app = await utils.initTestServer({ endpointGroups: ['eventBus'] });
|
|
|
|
|
2023-01-04 00:47:48 -08:00
|
|
|
globalOwnerRole = await testDb.getGlobalOwnerRole();
|
|
|
|
owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
|
|
|
|
|
|
|
unAuthOwnerAgent = utils.createAgent(app, {
|
|
|
|
apiPath: 'internal',
|
|
|
|
auth: false,
|
|
|
|
user: owner,
|
|
|
|
version: 1,
|
|
|
|
});
|
|
|
|
|
|
|
|
authOwnerAgent = utils.createAgent(app, {
|
|
|
|
apiPath: 'internal',
|
|
|
|
auth: true,
|
|
|
|
user: owner,
|
|
|
|
version: 1,
|
|
|
|
});
|
|
|
|
|
|
|
|
mockedSyslog.createClient.mockImplementation(() => new syslog.Client());
|
|
|
|
|
2023-05-23 17:01:45 -07:00
|
|
|
await utils.initConfigFile();
|
2023-01-04 00:47:48 -08:00
|
|
|
config.set('eventBus.logWriter.logBaseName', 'n8n-test-logwriter');
|
2023-03-27 03:30:03 -07:00
|
|
|
config.set('eventBus.logWriter.keepLogCount', 1);
|
2023-01-04 00:47:48 -08:00
|
|
|
config.set('userManagement.isInstanceOwnerSetUp', true);
|
2023-03-17 09:24:05 -07:00
|
|
|
|
|
|
|
await eventBus.initialize();
|
2023-01-04 00:47:48 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
afterAll(async () => {
|
2023-01-13 06:39:25 -08:00
|
|
|
jest.mock('@/eventbus/MessageEventBus/MessageEventBus');
|
2023-01-13 09:12:22 -08:00
|
|
|
await testDb.terminate();
|
2023-01-04 00:47:48 -08:00
|
|
|
await eventBus.close();
|
|
|
|
});
|
|
|
|
|
2023-01-13 06:39:25 -08:00
|
|
|
test('should have a running logwriter process', () => {
|
|
|
|
const thread = eventBus.logWriter.worker;
|
2023-01-04 00:47:48 -08:00
|
|
|
expect(thread).toBeDefined();
|
|
|
|
});
|
|
|
|
|
|
|
|
test('should have logwriter log messages', async () => {
|
2023-02-17 01:54:07 -08:00
|
|
|
const testMessage = new EventMessageGeneric({
|
|
|
|
eventName: 'n8n.test.message' as EventNamesTypes,
|
|
|
|
id: uuid(),
|
|
|
|
});
|
2023-01-04 00:47:48 -08:00
|
|
|
await eventBus.send(testMessage);
|
2023-01-13 06:39:25 -08:00
|
|
|
await new Promise((resolve) => {
|
|
|
|
eventBus.logWriter.worker?.once('message', async (msg: { command: string; data: any }) => {
|
|
|
|
expect(msg.command).toBe('appendMessageToLog');
|
|
|
|
expect(msg.data).toBe(true);
|
|
|
|
await confirmIdInAll(testMessage.id);
|
|
|
|
resolve(true);
|
|
|
|
});
|
|
|
|
});
|
2023-01-04 00:47:48 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
test('GET /eventbus/destination should fail due to missing authentication', async () => {
|
|
|
|
const response = await unAuthOwnerAgent.get('/eventbus/destination');
|
|
|
|
expect(response.statusCode).toBe(401);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('POST /eventbus/destination create syslog destination', async () => {
|
|
|
|
const response = await authOwnerAgent.post('/eventbus/destination').send(testSyslogDestination);
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('POST /eventbus/destination create sentry destination', async () => {
|
|
|
|
const response = await authOwnerAgent.post('/eventbus/destination').send(testSentryDestination);
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('POST /eventbus/destination create webhook destination', async () => {
|
|
|
|
const response = await authOwnerAgent.post('/eventbus/destination').send(testWebhookDestination);
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
|
|
});
|
|
|
|
|
|
|
|
test('GET /eventbus/destination all returned destinations should exist in eventbus', async () => {
|
|
|
|
const response = await authOwnerAgent.get('/eventbus/destination');
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
|
|
|
|
|
|
const data = response.body.data;
|
|
|
|
expect(data).toBeTruthy();
|
|
|
|
expect(Array.isArray(data)).toBeTruthy();
|
|
|
|
|
|
|
|
for (let index = 0; index < data.length; index++) {
|
|
|
|
const destination = data[index];
|
|
|
|
const foundDestinations = await eventBus.findDestination(destination.id);
|
|
|
|
expect(Array.isArray(foundDestinations)).toBeTruthy();
|
|
|
|
expect(foundDestinations.length).toBe(1);
|
|
|
|
expect(foundDestinations[0].label).toBe(destination.label);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-02-17 01:54:07 -08:00
|
|
|
// this test (presumably the mocking) is causing the test suite to randomly fail
|
2023-05-24 00:45:37 -07:00
|
|
|
// eslint-disable-next-line n8n-local-rules/no-skipped-tests
|
2023-01-20 02:02:53 -08:00
|
|
|
test.skip('should send message to syslog', async () => {
|
2023-02-17 01:54:07 -08:00
|
|
|
const testMessage = new EventMessageGeneric({
|
|
|
|
eventName: 'n8n.test.message' as EventNamesTypes,
|
|
|
|
id: uuid(),
|
|
|
|
});
|
2023-01-04 00:47:48 -08:00
|
|
|
|
|
|
|
const syslogDestination = eventBus.destinations[
|
|
|
|
testSyslogDestination.id!
|
|
|
|
] as MessageEventBusDestinationSyslog;
|
|
|
|
|
|
|
|
syslogDestination.enable();
|
|
|
|
|
|
|
|
const mockedSyslogClientLog = jest.spyOn(syslogDestination.client, 'log');
|
|
|
|
mockedSyslogClientLog.mockImplementation((_m, _options, _cb) => {
|
|
|
|
eventBus.confirmSent(testMessage, {
|
|
|
|
id: syslogDestination.id,
|
|
|
|
name: syslogDestination.label,
|
|
|
|
});
|
|
|
|
return syslogDestination.client;
|
|
|
|
});
|
|
|
|
|
|
|
|
await eventBus.send(testMessage);
|
2023-01-13 06:39:25 -08:00
|
|
|
await new Promise((resolve) => {
|
|
|
|
eventBus.logWriter.worker?.on(
|
|
|
|
'message',
|
|
|
|
async function handler001(msg: { command: string; data: any }) {
|
|
|
|
if (msg.command === 'appendMessageToLog') {
|
|
|
|
await confirmIdInAll(testMessage.id);
|
|
|
|
} else if (msg.command === 'confirmMessageSent') {
|
|
|
|
await confirmIdSent(testMessage.id);
|
|
|
|
expect(mockedSyslogClientLog).toHaveBeenCalled();
|
|
|
|
syslogDestination.disable();
|
|
|
|
eventBus.logWriter.worker?.removeListener('message', handler001);
|
|
|
|
resolve(true);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
2023-01-11 05:09:09 -08:00
|
|
|
});
|
|
|
|
|
2023-05-24 00:45:37 -07:00
|
|
|
// eslint-disable-next-line n8n-local-rules/no-skipped-tests
|
2023-01-20 02:02:53 -08:00
|
|
|
test.skip('should confirm send message if there are no subscribers', async () => {
|
2023-01-13 06:39:25 -08:00
|
|
|
const testMessageUnsubscribed = new EventMessageGeneric({
|
2023-02-17 01:54:07 -08:00
|
|
|
eventName: 'n8n.test.unsub' as EventNamesTypes,
|
2023-01-13 06:39:25 -08:00
|
|
|
id: uuid(),
|
|
|
|
});
|
2023-01-11 05:09:09 -08:00
|
|
|
|
|
|
|
const syslogDestination = eventBus.destinations[
|
|
|
|
testSyslogDestination.id!
|
|
|
|
] as MessageEventBusDestinationSyslog;
|
|
|
|
|
|
|
|
syslogDestination.enable();
|
|
|
|
|
2023-01-04 00:47:48 -08:00
|
|
|
await eventBus.send(testMessageUnsubscribed);
|
|
|
|
|
2023-01-13 06:39:25 -08:00
|
|
|
await new Promise((resolve) => {
|
|
|
|
eventBus.logWriter.worker?.on(
|
|
|
|
'message',
|
|
|
|
async function handler002(msg: { command: string; data: any }) {
|
|
|
|
if (msg.command === 'appendMessageToLog') {
|
|
|
|
await confirmIdInAll(testMessageUnsubscribed.id);
|
|
|
|
} else if (msg.command === 'confirmMessageSent') {
|
|
|
|
await confirmIdSent(testMessageUnsubscribed.id);
|
|
|
|
syslogDestination.disable();
|
|
|
|
eventBus.logWriter.worker?.removeListener('message', handler002);
|
|
|
|
resolve(true);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
2023-01-04 00:47:48 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
test('should anonymize audit message to syslog ', async () => {
|
2023-01-13 06:39:25 -08:00
|
|
|
const testAuditMessage = new EventMessageAudit({
|
|
|
|
eventName: 'n8n.audit.user.updated',
|
|
|
|
payload: {
|
|
|
|
_secret: 'secret',
|
|
|
|
public: 'public',
|
|
|
|
},
|
|
|
|
id: uuid(),
|
|
|
|
});
|
2023-01-04 00:47:48 -08:00
|
|
|
|
|
|
|
const syslogDestination = eventBus.destinations[
|
|
|
|
testSyslogDestination.id!
|
|
|
|
] as MessageEventBusDestinationSyslog;
|
|
|
|
|
|
|
|
syslogDestination.enable();
|
|
|
|
|
|
|
|
const mockedSyslogClientLog = jest.spyOn(syslogDestination.client, 'log');
|
|
|
|
mockedSyslogClientLog.mockImplementation((m, _options, _cb) => {
|
|
|
|
const o = JSON.parse(m);
|
|
|
|
expect(o).toHaveProperty('payload');
|
|
|
|
expect(o.payload).toHaveProperty('_secret');
|
|
|
|
syslogDestination.anonymizeAuditMessages
|
|
|
|
? expect(o.payload._secret).toBe('*')
|
|
|
|
: expect(o.payload._secret).toBe('secret');
|
|
|
|
expect(o.payload).toHaveProperty('public');
|
|
|
|
expect(o.payload.public).toBe('public');
|
|
|
|
return syslogDestination.client;
|
|
|
|
});
|
|
|
|
|
|
|
|
syslogDestination.anonymizeAuditMessages = true;
|
|
|
|
await eventBus.send(testAuditMessage);
|
2023-01-13 06:39:25 -08:00
|
|
|
await new Promise((resolve) => {
|
|
|
|
eventBus.logWriter.worker?.on(
|
|
|
|
'message',
|
|
|
|
async function handler005(msg: { command: string; data: any }) {
|
|
|
|
if (msg.command === 'appendMessageToLog') {
|
|
|
|
const sent = await eventBus.getEventsAll();
|
|
|
|
await confirmIdInAll(testAuditMessage.id);
|
|
|
|
expect(mockedSyslogClientLog).toHaveBeenCalled();
|
|
|
|
eventBus.logWriter.worker?.removeListener('message', handler005);
|
|
|
|
resolve(true);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
2023-01-04 00:47:48 -08:00
|
|
|
|
|
|
|
syslogDestination.anonymizeAuditMessages = false;
|
|
|
|
await eventBus.send(testAuditMessage);
|
2023-01-13 06:39:25 -08:00
|
|
|
await new Promise((resolve) => {
|
|
|
|
eventBus.logWriter.worker?.on(
|
|
|
|
'message',
|
|
|
|
async function handler006(msg: { command: string; data: any }) {
|
|
|
|
if (msg.command === 'appendMessageToLog') {
|
|
|
|
const sent = await eventBus.getEventsAll();
|
|
|
|
await confirmIdInAll(testAuditMessage.id);
|
|
|
|
expect(mockedSyslogClientLog).toHaveBeenCalled();
|
|
|
|
syslogDestination.disable();
|
|
|
|
eventBus.logWriter.worker?.removeListener('message', handler006);
|
|
|
|
resolve(true);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
2023-01-04 00:47:48 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
test('should send message to webhook ', async () => {
|
2023-02-17 01:54:07 -08:00
|
|
|
const testMessage = new EventMessageGeneric({
|
|
|
|
eventName: 'n8n.test.message' as EventNamesTypes,
|
|
|
|
id: uuid(),
|
|
|
|
});
|
2023-01-04 00:47:48 -08:00
|
|
|
|
|
|
|
const webhookDestination = eventBus.destinations[
|
|
|
|
testWebhookDestination.id!
|
|
|
|
] as MessageEventBusDestinationWebhook;
|
|
|
|
|
|
|
|
webhookDestination.enable();
|
|
|
|
|
|
|
|
mockedAxios.post.mockResolvedValue({ status: 200, data: { msg: 'OK' } });
|
|
|
|
mockedAxios.request.mockResolvedValue({ status: 200, data: { msg: 'OK' } });
|
|
|
|
|
|
|
|
await eventBus.send(testMessage);
|
2023-01-13 06:39:25 -08:00
|
|
|
await new Promise((resolve) => {
|
|
|
|
eventBus.logWriter.worker?.on(
|
|
|
|
'message',
|
|
|
|
async function handler003(msg: { command: string; data: any }) {
|
|
|
|
if (msg.command === 'appendMessageToLog') {
|
|
|
|
await confirmIdInAll(testMessage.id);
|
|
|
|
} else if (msg.command === 'confirmMessageSent') {
|
|
|
|
await confirmIdSent(testMessage.id);
|
|
|
|
expect(mockedAxios.request).toHaveBeenCalled();
|
|
|
|
webhookDestination.disable();
|
|
|
|
eventBus.logWriter.worker?.removeListener('message', handler003);
|
|
|
|
resolve(true);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
2023-01-04 00:47:48 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
test('should send message to sentry ', async () => {
|
2023-02-17 01:54:07 -08:00
|
|
|
const testMessage = new EventMessageGeneric({
|
|
|
|
eventName: 'n8n.test.message' as EventNamesTypes,
|
|
|
|
id: uuid(),
|
|
|
|
});
|
2023-01-04 00:47:48 -08:00
|
|
|
|
|
|
|
const sentryDestination = eventBus.destinations[
|
|
|
|
testSentryDestination.id!
|
|
|
|
] as MessageEventBusDestinationSentry;
|
|
|
|
|
|
|
|
sentryDestination.enable();
|
|
|
|
|
2023-01-23 08:41:55 -08:00
|
|
|
const mockedSentryCaptureMessage = jest.spyOn(sentryDestination.sentryClient!, 'captureMessage');
|
2023-01-04 00:47:48 -08:00
|
|
|
mockedSentryCaptureMessage.mockImplementation((_m, _level, _hint, _scope) => {
|
|
|
|
eventBus.confirmSent(testMessage, {
|
|
|
|
id: sentryDestination.id,
|
|
|
|
name: sentryDestination.label,
|
|
|
|
});
|
|
|
|
return testMessage.id;
|
|
|
|
});
|
|
|
|
|
|
|
|
await eventBus.send(testMessage);
|
2023-01-13 06:39:25 -08:00
|
|
|
await new Promise((resolve) => {
|
|
|
|
eventBus.logWriter.worker?.on(
|
|
|
|
'message',
|
|
|
|
async function handler004(msg: { command: string; data: any }) {
|
|
|
|
if (msg.command === 'appendMessageToLog') {
|
|
|
|
await confirmIdInAll(testMessage.id);
|
|
|
|
} else if (msg.command === 'confirmMessageSent') {
|
|
|
|
await confirmIdSent(testMessage.id);
|
|
|
|
expect(mockedSentryCaptureMessage).toHaveBeenCalled();
|
|
|
|
sentryDestination.disable();
|
|
|
|
eventBus.logWriter.worker?.removeListener('message', handler004);
|
|
|
|
resolve(true);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
2023-01-04 00:47:48 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
test('DEL /eventbus/destination delete all destinations by id', async () => {
|
|
|
|
const existingDestinationIds = [...Object.keys(eventBus.destinations)];
|
|
|
|
|
|
|
|
await Promise.all(
|
|
|
|
existingDestinationIds.map(async (id) => {
|
|
|
|
const response = await authOwnerAgent.del('/eventbus/destination').query({ id });
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(Object.keys(eventBus.destinations).length).toBe(0);
|
|
|
|
});
|