mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
fix(MQTT Node): Close connection if connection attempt fails (#10873)
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
Benchmark Docker Image CI / build (push) Waiting to run
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
Benchmark Docker Image CI / build (push) Waiting to run
This commit is contained in:
parent
0a317b7072
commit
ee7147c6b3
|
@ -1,5 +1,6 @@
|
||||||
import { connect, type IClientOptions, type MqttClient } from 'mqtt';
|
import { connect, type IClientOptions, type MqttClient } from 'mqtt';
|
||||||
import { ApplicationError, randomString } from 'n8n-workflow';
|
import { ApplicationError, randomString } from 'n8n-workflow';
|
||||||
|
|
||||||
import { formatPrivateKey } from '@utils/utilities';
|
import { formatPrivateKey } from '@utils/utilities';
|
||||||
|
|
||||||
interface BaseMqttCredential {
|
interface BaseMqttCredential {
|
||||||
|
@ -62,6 +63,10 @@ export const createClient = async (credentials: MqttCredential): Promise<MqttCli
|
||||||
const onError = (error: Error) => {
|
const onError = (error: Error) => {
|
||||||
client.removeListener('connect', onConnect);
|
client.removeListener('connect', onConnect);
|
||||||
client.removeListener('error', onError);
|
client.removeListener('error', onError);
|
||||||
|
// mqtt client has an automatic reconnect mechanism that will
|
||||||
|
// keep trying to reconnect until it succeeds unless we
|
||||||
|
// explicitly close the client
|
||||||
|
client.end();
|
||||||
reject(new ApplicationError(error.message));
|
reject(new ApplicationError(error.message));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
type INodeType,
|
type INodeType,
|
||||||
type INodeTypeDescription,
|
type INodeTypeDescription,
|
||||||
NodeConnectionType,
|
NodeConnectionType,
|
||||||
|
ensureError,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
import { createClient, type MqttCredential } from './GenericFunctions';
|
import { createClient, type MqttCredential } from './GenericFunctions';
|
||||||
|
@ -116,10 +117,12 @@ export class Mqtt implements INodeType {
|
||||||
try {
|
try {
|
||||||
const client = await createClient(credentials);
|
const client = await createClient(credentials);
|
||||||
client.end();
|
client.end();
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
|
const error = ensureError(e);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 'Error',
|
status: 'Error',
|
||||||
message: (error as Error).message,
|
message: error.message,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import { MqttClient } from 'mqtt';
|
|
||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import { MqttClient } from 'mqtt';
|
||||||
|
import { ApplicationError } from 'n8n-workflow';
|
||||||
|
|
||||||
import { createClient, type MqttCredential } from '../GenericFunctions';
|
import { createClient, type MqttCredential } from '../GenericFunctions';
|
||||||
|
|
||||||
describe('createClient', () => {
|
describe('createClient', () => {
|
||||||
|
beforeEach(() => jest.clearAllMocks());
|
||||||
|
|
||||||
|
it('should create a client with minimal credentials', async () => {
|
||||||
const mockConnect = jest.spyOn(MqttClient.prototype, 'connect').mockImplementation(function (
|
const mockConnect = jest.spyOn(MqttClient.prototype, 'connect').mockImplementation(function (
|
||||||
this: MqttClient,
|
this: MqttClient,
|
||||||
) {
|
) {
|
||||||
|
@ -11,9 +15,6 @@ describe('createClient', () => {
|
||||||
return this;
|
return this;
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => jest.clearAllMocks());
|
|
||||||
|
|
||||||
it('should create a client with minimal credentials', async () => {
|
|
||||||
const credentials = mock<MqttCredential>({
|
const credentials = mock<MqttCredential>({
|
||||||
protocol: 'mqtt',
|
protocol: 'mqtt',
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
|
@ -35,4 +36,31 @@ describe('createClient', () => {
|
||||||
clientId: 'testClient',
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue