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,66 @@
import { mock } from 'jest-mock-extended';
import { MqttClient } from 'mqtt';
import { ApplicationError } from '@n8n/errors';
import { createClient, type MqttCredential } from '../GenericFunctions';
describe('createClient', () => {
beforeEach(() => jest.clearAllMocks());
it('should create a client with minimal credentials', async () => {
const mockConnect = jest.spyOn(MqttClient.prototype, 'connect').mockImplementation(function (
this: MqttClient,
) {
setImmediate(() => this.emit('connect', mock()));
return this;
});
const credentials = mock<MqttCredential>({
protocol: 'mqtt',
host: 'localhost',
port: 1883,
clean: true,
clientId: 'testClient',
ssl: false,
});
const client = await createClient(credentials);
expect(mockConnect).toBeCalledTimes(1);
expect(client).toBeDefined();
expect(client).toBeInstanceOf(MqttClient);
expect(client.options).toMatchObject({
protocol: 'mqtt',
host: 'localhost',
port: 1883,
clean: true,
clientId: 'testClient',
});
});
it('should reject with ApplicationError on connection error and close connection', async () => {
const mockConnect = jest.spyOn(MqttClient.prototype, 'connect').mockImplementation(function (
this: MqttClient,
) {
setImmediate(() => this.emit('error', new Error('Connection failed')));
return this;
});
const mockEnd = jest.spyOn(MqttClient.prototype, 'end').mockImplementation();
const credentials: MqttCredential = {
protocol: 'mqtt',
host: 'localhost',
port: 1883,
clean: true,
clientId: 'testClientId',
username: 'testUser',
password: 'testPass',
ssl: false,
};
const clientPromise = createClient(credentials);
await expect(clientPromise).rejects.toThrow(ApplicationError);
expect(mockConnect).toBeCalledTimes(1);
expect(mockEnd).toBeCalledTimes(1);
});
});
@@ -0,0 +1,56 @@
import { mock } from 'jest-mock-extended';
import type { MqttClient } from 'mqtt';
import type { ICredentialDataDecryptedObject, IExecuteFunctions } from 'n8n-workflow';
import { createClient } from '../GenericFunctions';
import { Mqtt } from '../Mqtt.node';
jest.mock('../GenericFunctions', () => {
const mockMqttClient = mock<MqttClient>();
return {
createClient: jest.fn().mockResolvedValue(mockMqttClient),
};
});
describe('MQTT Node', () => {
const credentials = mock<ICredentialDataDecryptedObject>();
const executeFunctions = mock<IExecuteFunctions>();
beforeEach(() => {
jest.clearAllMocks();
executeFunctions.getCredentials.calledWith('mqtt').mockResolvedValue(credentials);
executeFunctions.getInputData.mockReturnValue([{ json: { testing: true } }]);
executeFunctions.getNodeParameter.calledWith('topic', 0).mockReturnValue('test/topic');
executeFunctions.getNodeParameter.calledWith('options', 0).mockReturnValue({});
});
it('should publish input data', async () => {
executeFunctions.getNodeParameter.calledWith('sendInputData', 0).mockReturnValue(true);
const result = await new Mqtt().execute.call(executeFunctions);
expect(result).toEqual([[{ json: { testing: true } }]]);
expect(executeFunctions.getCredentials).toHaveBeenCalledTimes(1);
expect(executeFunctions.getNodeParameter).toHaveBeenCalledTimes(3);
const mockMqttClient = await createClient(mock());
expect(mockMqttClient.publishAsync).toHaveBeenCalledWith('test/topic', '{"testing":true}', {});
expect(mockMqttClient.endAsync).toHaveBeenCalledTimes(1);
});
it('should publish a custom message', async () => {
executeFunctions.getNodeParameter.calledWith('sendInputData', 0).mockReturnValue(false);
executeFunctions.getNodeParameter.calledWith('message', 0).mockReturnValue('Hello, MQTT!');
const result = await new Mqtt().execute.call(executeFunctions);
expect(result).toEqual([[{ json: { testing: true } }]]);
expect(executeFunctions.getCredentials).toHaveBeenCalledTimes(1);
expect(executeFunctions.getNodeParameter).toHaveBeenCalledTimes(4);
const mockMqttClient = await createClient(mock());
expect(mockMqttClient.publishAsync).toHaveBeenCalledWith('test/topic', 'Hello, MQTT!', {});
expect(mockMqttClient.endAsync).toHaveBeenCalledTimes(1);
});
});
@@ -0,0 +1,123 @@
import { captor, mock } from 'jest-mock-extended';
import type { MqttClient, OnMessageCallback } from 'mqtt';
import { returnJsonArray } from 'n8n-core';
import type { ICredentialDataDecryptedObject, ITriggerFunctions } from 'n8n-workflow';
import { createClient } from '../GenericFunctions';
import { MqttTrigger } from '../MqttTrigger.node';
jest.mock('../GenericFunctions', () => {
const mockMqttClient = mock<MqttClient>();
return {
createClient: jest.fn().mockResolvedValue(mockMqttClient),
};
});
describe('MQTT Trigger Node', () => {
const topic = 'test/topic';
const payload = Buffer.from('{"testing": true}');
const credentials = mock<ICredentialDataDecryptedObject>();
const triggerFunctions = mock<ITriggerFunctions>({
helpers: { returnJsonArray },
});
beforeEach(() => {
jest.clearAllMocks();
triggerFunctions.getCredentials.calledWith('mqtt').mockResolvedValue(credentials);
triggerFunctions.getNodeParameter.calledWith('topics').mockReturnValue(topic);
});
it('should emit in manual mode', async () => {
triggerFunctions.getMode.mockReturnValue('manual');
triggerFunctions.getNodeParameter.calledWith('options').mockReturnValue({});
const response = await new MqttTrigger().trigger.call(triggerFunctions);
expect(response.manualTriggerFunction).toBeDefined();
expect(response.closeFunction).toBeDefined();
expect(triggerFunctions.getCredentials).toHaveBeenCalledTimes(1);
expect(triggerFunctions.getNodeParameter).toHaveBeenCalledTimes(2);
// manually trigger the node, like Workflow.runNode does
const triggerPromise = response.manualTriggerFunction!();
const mockMqttClient = await createClient(mock());
expect(mockMqttClient.on).not.toHaveBeenCalled();
const onMessageCaptor = captor<OnMessageCallback>();
expect(mockMqttClient.once).toHaveBeenCalledWith('message', onMessageCaptor);
expect(mockMqttClient.subscribeAsync).toHaveBeenCalledWith({ [topic]: { qos: 0 } });
expect(triggerFunctions.emit).not.toHaveBeenCalled();
// simulate a message
const onMessage = onMessageCaptor.value;
onMessage('test/topic', payload, mock());
expect(triggerFunctions.emit).toHaveBeenCalledWith([
[{ json: { message: '{"testing": true}', topic } }],
]);
// wait for the promise to resolve
await new Promise((resolve) => setImmediate(resolve));
await expect(triggerPromise).resolves.toEqual(undefined);
expect(mockMqttClient.endAsync).not.toHaveBeenCalled();
await response.closeFunction!();
expect(mockMqttClient.endAsync).toHaveBeenCalledTimes(1);
});
it('should emit in trigger mode', async () => {
triggerFunctions.getMode.mockReturnValue('trigger');
triggerFunctions.getNodeParameter.calledWith('options').mockReturnValue({});
const response = await new MqttTrigger().trigger.call(triggerFunctions);
expect(response.manualTriggerFunction).toBeDefined();
expect(response.closeFunction).toBeDefined();
expect(triggerFunctions.getCredentials).toHaveBeenCalledTimes(1);
expect(triggerFunctions.getNodeParameter).toHaveBeenCalledTimes(2);
const mockMqttClient = await createClient(mock());
expect(mockMqttClient.once).not.toHaveBeenCalled();
const onMessageCaptor = captor<OnMessageCallback>();
expect(mockMqttClient.on).toHaveBeenCalledWith('message', onMessageCaptor);
expect(mockMqttClient.subscribeAsync).toHaveBeenCalledWith({ [topic]: { qos: 0 } });
expect(triggerFunctions.emit).not.toHaveBeenCalled();
// simulate a message
const onMessage = onMessageCaptor.value;
onMessage('test/topic', payload, mock());
expect(triggerFunctions.emit).toHaveBeenCalledWith(
[[{ json: { message: '{"testing": true}', topic } }]],
undefined,
undefined,
);
expect(mockMqttClient.endAsync).not.toHaveBeenCalled();
await response.closeFunction!();
expect(mockMqttClient.endAsync).toHaveBeenCalledTimes(1);
});
it('should parse JSON messages when configured', async () => {
triggerFunctions.getMode.mockReturnValue('trigger');
triggerFunctions.getNodeParameter.calledWith('options').mockReturnValue({
jsonParseBody: true,
});
await new MqttTrigger().trigger.call(triggerFunctions);
const mockMqttClient = await createClient(mock());
const onMessageCaptor = captor<OnMessageCallback>();
expect(mockMqttClient.on).toHaveBeenCalledWith('message', onMessageCaptor);
// simulate a message
const onMessage = onMessageCaptor.value;
onMessage('test/topic', payload, mock());
expect(triggerFunctions.emit).toHaveBeenCalledWith(
[[{ json: { message: { testing: true }, topic } }]],
undefined,
undefined,
);
});
});