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
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:
@@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user