From f8ab4dc9604f55ced0c414102c4f636d6ed38940 Mon Sep 17 00:00:00 2001 From: Nerivec <62446222+Nerivec@users.noreply.github.com> Date: Fri, 15 Nov 2024 15:37:02 +0100 Subject: [PATCH 01/13] fix: Use async for mqtt. --- lib/controller.ts | 13 +++-- lib/extension/homeassistant.ts | 8 +-- lib/mqtt.ts | 102 +++++++++++++++------------------ lib/types/types.d.ts | 6 -- lib/util/utils.ts | 2 +- 5 files changed, 57 insertions(+), 74 deletions(-) diff --git a/lib/controller.ts b/lib/controller.ts index 8d5ecde8ca..94b3037b49 100644 --- a/lib/controller.ts +++ b/lib/controller.ts @@ -1,3 +1,4 @@ +import type {IClientPublishOptions} from 'mqtt'; import type * as SdNotify from 'sd-notify'; import assert from 'assert'; @@ -265,14 +266,14 @@ export class Controller { message = newState; } - const options: MQTTOptions = { - retain: utils.getObjectProperty(entity.options, 'retain', false) as boolean, - qos: utils.getObjectProperty(entity.options, 'qos', 0) as 0 | 1 | 2, + const options: IClientPublishOptions = { + retain: utils.getObjectProperty(entity.options, 'retain', false), + qos: utils.getObjectProperty(entity.options, 'qos', 0), }; + const retention = utils.getObjectProperty(entity.options, 'retention', false); - const retention = utils.getObjectProperty(entity.options, 'retention', false); if (retention !== false) { - options.properties = {messageExpiryInterval: retention as number}; + options.properties = {messageExpiryInterval: retention}; } if (entity.isDevice() && settings.get().mqtt.include_device_information) { @@ -328,7 +329,7 @@ export class Controller { this.eventBus.emitPublishEntityState({entity, message, stateChangeReason, payload}); } - async iteratePayloadAttributeOutput(topicRoot: string, payload: KeyValue, options: MQTTOptions): Promise { + async iteratePayloadAttributeOutput(topicRoot: string, payload: KeyValue, options: IClientPublishOptions): Promise { for (const [key, value] of Object.entries(payload)) { let subPayload = value; let message = null; diff --git a/lib/extension/homeassistant.ts b/lib/extension/homeassistant.ts index f915ad6500..e25495102b 100644 --- a/lib/extension/homeassistant.ts +++ b/lib/extension/homeassistant.ts @@ -440,8 +440,8 @@ export default class HomeAssistant extends Extension { this.eventBus.onEntityOptionsChanged(this, async (data) => await this.discover(data.entity)); this.eventBus.onExposesChanged(this, async (data) => await this.discover(data.device)); - this.mqtt.subscribe(this.statusTopic); - this.mqtt.subscribe(DEFAULT_STATUS_TOPIC); + await this.mqtt.subscribe(this.statusTopic); + await this.mqtt.subscribe(DEFAULT_STATUS_TOPIC); /** * Prevent unnecessary re-discovery of entities by waiting 5 seconds for retained discovery messages to come in. @@ -458,9 +458,9 @@ export default class HomeAssistant extends Extension { } logger.debug(`Discovering entities to Home Assistant in ${discoverWait}s`); - this.mqtt.subscribe(`${this.discoveryTopic}/#`); + await this.mqtt.subscribe(`${this.discoveryTopic}/#`); setTimeout(async () => { - this.mqtt.unsubscribe(`${this.discoveryTopic}/#`); + await this.mqtt.unsubscribe(`${this.discoveryTopic}/#`); logger.debug(`Discovering entities to Home Assistant`); await this.discover(this.bridge); diff --git a/lib/mqtt.ts b/lib/mqtt.ts index 3e6245634d..8625a06570 100644 --- a/lib/mqtt.ts +++ b/lib/mqtt.ts @@ -1,7 +1,9 @@ +import type {IClientOptions, IClientPublishOptions, MqttClient} from 'mqtt'; + import fs from 'fs'; import bind from 'bind-decorator'; -import * as mqtt from 'mqtt'; +import {connectAsync} from 'mqtt'; import logger from './util/logger'; import * as settings from './util/settings'; @@ -12,13 +14,12 @@ const NS = 'z2m:mqtt'; export default class MQTT { private publishedTopics: Set = new Set(); private connectionTimer?: NodeJS.Timeout; - // @ts-expect-error initialized in `connect` - private client: mqtt.MqttClient; + private client!: MqttClient; private eventBus: EventBus; private initialConnect = true; private republishRetainedTimer?: NodeJS.Timeout; public retainedMessages: { - [s: string]: {payload: string; options: MQTTOptions; skipLog: boolean; skipReceive: boolean; topic: string; base: string}; + [s: string]: {payload: string; options: IClientPublishOptions; skipLog: boolean; skipReceive: boolean; topic: string; base: string}; } = {}; constructor(eventBus: EventBus) { @@ -30,7 +31,7 @@ export default class MQTT { logger.info(`Connecting to MQTT server at ${mqttSettings.server}`); - const options: mqtt.IClientOptions = { + const options: IClientOptions = { will: { topic: `${settings.get().mqtt.base_topic}/bridge/state`, payload: Buffer.from(JSON.stringify({state: 'offline'})), @@ -78,49 +79,32 @@ export default class MQTT { options.rejectUnauthorized = false; } - return await new Promise((resolve, reject) => { - this.client = mqtt.connect(mqttSettings.server, options); - // https://github.com/Koenkk/zigbee2mqtt/issues/9822 - this.client.stream.setMaxListeners(0); - - this.client.on('connect', async () => { - // Set timer at interval to check if connected to MQTT server. - clearTimeout(this.connectionTimer); - this.connectionTimer = setInterval(() => { - if (this.client.reconnecting) { - logger.error('Not connected to MQTT server!'); - } - }, utils.seconds(10)); - - logger.info('Connected to MQTT server'); - await this.publishStateOnline(); - - if (!this.initialConnect) { - this.republishRetainedTimer = setTimeout(async () => { - // Republish retained messages in case MQTT broker does not persist them. - // https://github.com/Koenkk/zigbee2mqtt/issues/9629 - for (const msg of Object.values(this.retainedMessages)) { - await this.publish(msg.topic, msg.payload, msg.options, msg.base, msg.skipLog, msg.skipReceive); - } - }, 2000); - } - - this.initialConnect = false; - this.subscribe(`${settings.get().mqtt.base_topic}/#`); - resolve(); - }); - - this.client.on('error', (err) => { - logger.error(`MQTT error: ${err.message}`); - reject(err); - }); - - this.client.on('message', this.onMessage); + this.client = await connectAsync(mqttSettings.server, options); + + await this.onConnect(); + // https://github.com/Koenkk/zigbee2mqtt/issues/9822 + this.client.stream.setMaxListeners(0); + + this.client.on('connect', this.onConnect); + this.client.on('error', (err) => { + logger.error(`MQTT error: ${err.message}`); }); - } + this.client.on('message', this.onMessage); - @bind async publishStateOnline(): Promise { - await this.publish('bridge/state', JSON.stringify({state: 'online'}), {retain: true, qos: 0}); + this.republishRetainedTimer = setTimeout(async () => { + // Republish retained messages in case MQTT broker does not persist them. + // https://github.com/Koenkk/zigbee2mqtt/issues/9629 + for (const msg of Object.values(this.retainedMessages)) { + await this.publish(msg.topic, msg.payload, msg.options, msg.base, msg.skipLog, msg.skipReceive); + } + }, 2000); + + // Set timer at interval to check if connected to MQTT server. + this.connectionTimer = setInterval(() => { + if (this.client.reconnecting) { + logger.error('Not connected to MQTT server!'); + } + }, utils.seconds(10)); } async disconnect(): Promise { @@ -129,15 +113,21 @@ export default class MQTT { await this.publish('bridge/state', JSON.stringify({state: 'offline'}), {retain: true, qos: 0}); this.eventBus.removeListeners(this); logger.info('Disconnecting from MQTT server'); - this.client?.end(); + await this.client?.endAsync(); + } + + async subscribe(topic: string): ReturnType { + return await this.client.subscribeAsync(topic); } - subscribe(topic: string): void { - this.client.subscribe(topic); + async unsubscribe(topic: string): ReturnType { + return await this.client.unsubscribeAsync(topic); } - unsubscribe(topic: string): void { - this.client.unsubscribe(topic); + @bind private async onConnect(): Promise { + logger.info('Connected to MQTT server'); + await this.publish('bridge/state', JSON.stringify({state: 'online'}), {retain: true, qos: 0}); + await this.subscribe(`${settings.get().mqtt.base_topic}/#`); } @bind public onMessage(topic: string, message: Buffer): void { @@ -161,12 +151,12 @@ export default class MQTT { async publish( topic: string, payload: string, - options: MQTTOptions = {}, + options: IClientPublishOptions = {}, base = settings.get().mqtt.base_topic, skipLog = false, skipReceive = true, ): Promise { - const defaultOptions: {qos: QoS; retain: boolean} = {qos: 0, retain: false}; + const defaultOptions = {qos: 0 as const, retain: false}; topic = `${base}/${topic}`; if (skipReceive) { @@ -197,14 +187,12 @@ export default class MQTT { logger.info(() => `MQTT publish: topic '${topic}', payload '${payload}'`, NS); } - const actualOptions: mqtt.IClientPublishOptions = {...defaultOptions, ...options}; + const actualOptions: IClientPublishOptions = {...defaultOptions, ...options}; if (settings.get().mqtt.force_disable_retain) { actualOptions.retain = false; } - return await new Promise((resolve) => { - this.client.publish(topic, payload, actualOptions, () => resolve()); - }); + await this.client.publishAsync(topic, payload, actualOptions); } } diff --git a/lib/types/types.d.ts b/lib/types/types.d.ts index 43ae69dbb6..45fa00fc01 100644 --- a/lib/types/types.d.ts +++ b/lib/types/types.d.ts @@ -30,7 +30,6 @@ declare global { type Device = TypeDevice; type State = TypeState; type Extension = TypeExtension; - type QoS = 0 | 1 | 2; // Types type ExternalDefinition = zhc.Definition & {homeassistant: unknown}; @@ -40,11 +39,6 @@ declare global { error?: string; transaction?: string; } - interface MQTTOptions { - qos?: QoS; - retain?: boolean; - properties?: {messageExpiryInterval: number}; - } type Scene = {id: number; name: string}; type StateChangeReason = 'publishDebounce' | 'groupOptimistic' | 'lastSeenChanged' | 'publishCached' | 'publishThrottle'; type PublishEntityState = (entity: Device | Group, payload: KeyValue, stateChangeReason?: StateChangeReason) => Promise; diff --git a/lib/util/utils.ts b/lib/util/utils.ts index 7985049725..f44e921581 100644 --- a/lib/util/utils.ts +++ b/lib/util/utils.ts @@ -119,7 +119,7 @@ function equalsPartial(object: KeyValue, expected: KeyValue): boolean { return true; } -function getObjectProperty(object: KeyValue, key: string, defaultValue: unknown): unknown { +function getObjectProperty(object: KeyValue, key: string, defaultValue: NoInfer): T { return object && object[key] !== undefined ? object[key] : defaultValue; } From 80353f8282a932a3fd336178cd2ba9a3cf99949e Mon Sep 17 00:00:00 2001 From: Nerivec <62446222+Nerivec@users.noreply.github.com> Date: Fri, 15 Nov 2024 16:30:56 +0100 Subject: [PATCH 02/13] Update mocks, fix expects --- lib/mqtt.ts | 30 +- test/controller.test.ts | 310 ++++++------ test/extensions/availability.test.ts | 69 ++- test/extensions/bind.test.ts | 73 +-- test/extensions/bridge.test.ts | 537 +++++++++------------ test/extensions/configure.test.ts | 14 +- test/extensions/externalConverters.test.ts | 4 +- test/extensions/externalExtension.test.ts | 34 +- test/extensions/frontend.test.ts | 9 +- test/extensions/groups.test.ts | 324 ++++++------- test/extensions/homeassistant.test.ts | 424 +++++++--------- test/extensions/networkMap.test.ts | 43 +- test/extensions/onEvent.test.ts | 2 +- test/extensions/otaUpdate.test.ts | 54 +-- test/extensions/publish.test.ts | 419 +++++++--------- test/extensions/receive.test.ts | 250 +++++----- test/mocks/mqtt.ts | 17 +- 17 files changed, 1145 insertions(+), 1468 deletions(-) diff --git a/lib/mqtt.ts b/lib/mqtt.ts index 8625a06570..72c9c13d20 100644 --- a/lib/mqtt.ts +++ b/lib/mqtt.ts @@ -16,7 +16,6 @@ export default class MQTT { private connectionTimer?: NodeJS.Timeout; private client!: MqttClient; private eventBus: EventBus; - private initialConnect = true; private republishRetainedTimer?: NodeJS.Timeout; public retainedMessages: { [s: string]: {payload: string; options: IClientPublishOptions; skipLog: boolean; skipReceive: boolean; topic: string; base: string}; @@ -80,17 +79,16 @@ export default class MQTT { } this.client = await connectAsync(mqttSettings.server, options); - - await this.onConnect(); - // https://github.com/Koenkk/zigbee2mqtt/issues/9822 - this.client.stream.setMaxListeners(0); - - this.client.on('connect', this.onConnect); this.client.on('error', (err) => { logger.error(`MQTT error: ${err.message}`); }); this.client.on('message', this.onMessage); + await this.onConnect(); + this.client.on('connect', this.onConnect); + // https://github.com/Koenkk/zigbee2mqtt/issues/9822 + this.client.stream.setMaxListeners(0); + this.republishRetainedTimer = setTimeout(async () => { // Republish retained messages in case MQTT broker does not persist them. // https://github.com/Koenkk/zigbee2mqtt/issues/9629 @@ -116,12 +114,12 @@ export default class MQTT { await this.client?.endAsync(); } - async subscribe(topic: string): ReturnType { - return await this.client.subscribeAsync(topic); + async subscribe(topic: string): Promise { + await this.client.subscribeAsync(topic); } - async unsubscribe(topic: string): ReturnType { - return await this.client.unsubscribeAsync(topic); + async unsubscribe(topic: string): Promise { + await this.client.unsubscribeAsync(topic); } @bind private async onConnect(): Promise { @@ -193,6 +191,14 @@ export default class MQTT { actualOptions.retain = false; } - await this.client.publishAsync(topic, payload, actualOptions); + try { + await this.client.publishAsync(topic, payload, actualOptions); + } catch (error) { + /* istanbul ignore else */ + if (!skipLog) { + logger.error(`MQTT server error: ${(error as Error).message}`); + logger.error(`Could not send message: topic: '${topic}', payload: '${payload}`); + } + } } } diff --git a/test/controller.test.ts b/test/controller.test.ts index 671a8dd21c..8123b12273 100644 --- a/test/controller.test.ts +++ b/test/controller.test.ts @@ -1,7 +1,7 @@ import * as data from './mocks/data'; import {mockLogger} from './mocks/logger'; -import {mockMQTT, mockMQTTConnect, events as mockMQTTEvents} from './mocks/mqtt'; -import {EventHandler, flushPromises, JestMockAny} from './mocks/utils'; +import {mockMQTT, mockMQTTConnectAsync, events as mockMQTTEvents} from './mocks/mqtt'; +import {flushPromises, JestMockAny} from './mocks/utils'; import {devices, mockController as mockZHController, events as mockZHEvents, returnDevices} from './mocks/zigbeeHerdsman'; import fs from 'fs'; @@ -34,9 +34,12 @@ jest.mock( const mocksClear = [ mockZHController.stop, - mockMQTT.end, - mockMQTT.publish, - mockMQTTConnect, + mockMQTT.endAsync, + mockMQTT.publishAsync, + mockMQTT.subscribeAsync, + mockMQTT.unsubscribeAsync, + mockMQTT.endAsync, + mockMQTTConnectAsync, devices.bulb_color.removeFromNetwork, devices.bulb.removeFromNetwork, mockLogger.log, @@ -68,6 +71,10 @@ describe('Controller', () => { jest.useRealTimers(); }); + afterEach(async () => { + await controller?.stop(); + }) + it('Start controller', async () => { settings.set(['advanced', 'transmit_power'], 14); await controller.start(); @@ -92,21 +99,19 @@ describe('Controller', () => { ); expect(mockLogger.info).toHaveBeenCalledWith('remote (0x0017880104e45517): 324131092621 - Philips Hue dimmer switch (EndDevice)'); expect(mockLogger.info).toHaveBeenCalledWith('0x0017880104e45518 (0x0017880104e45518): Not supported (EndDevice)'); - expect(mockMQTTConnect).toHaveBeenCalledTimes(1); - expect(mockMQTTConnect).toHaveBeenCalledWith('mqtt://localhost', { + expect(mockMQTTConnectAsync).toHaveBeenCalledTimes(1); + expect(mockMQTTConnectAsync).toHaveBeenCalledWith('mqtt://localhost', { will: {payload: Buffer.from('{"state":"offline"}'), retain: true, topic: 'zigbee2mqtt/bridge/state', qos: 1}, }); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', stringify({state: 'ON', brightness: 50, color_temp: 370, linkquality: 99}), {retain: true, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/remote', stringify({brightness: 255}), {retain: true, qos: 0}, - expect.any(Function), ); }); @@ -134,7 +139,7 @@ describe('Controller', () => { settings.set(['mqtt'], configuration); await controller.start(); await flushPromises(); - expect(mockMQTTConnect).toHaveBeenCalledTimes(1); + expect(mockMQTTConnectAsync).toHaveBeenCalledTimes(1); const expected = { will: {payload: Buffer.from('{"state":"offline"}'), retain: true, topic: 'zigbee2mqtt/bridge/state', qos: 1}, keepalive: 30, @@ -147,7 +152,7 @@ describe('Controller', () => { rejectUnauthorized: false, protocolVersion: 5, }; - expect(mockMQTTConnect).toHaveBeenCalledWith('mqtt://localhost', expected); + expect(mockMQTTConnectAsync).toHaveBeenCalledWith('mqtt://localhost', expected); }); it('Should generate network_key, pan_id and ext_pan_id when set to GENERATE', async () => { @@ -168,19 +173,17 @@ describe('Controller', () => { data.writeDefaultState(); await controller.start(); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', stringify({state: 'ON', brightness: 50, color_temp: 370, linkquality: 99}), {qos: 0, retain: true}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/remote', stringify({brightness: 255}), {qos: 0, retain: true}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {qos: 0, retain: false}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {qos: 0, retain: false}); }); it('Start controller should not publish cached states when disabled', async () => { @@ -188,7 +191,7 @@ describe('Controller', () => { data.writeDefaultState(); await controller.start(); await flushPromises(); - const publishedTopics = mockMQTT.publish.mock.calls.map((m) => m[0]); + const publishedTopics = mockMQTT.publishAsync.mock.calls.map((m) => m[0]); expect(publishedTopics).toEqual(expect.not.arrayContaining(['zigbee2mqtt/bulb', 'zigbee2mqtt/remote'])); }); @@ -197,13 +200,12 @@ describe('Controller', () => { data.writeDefaultState(); await controller.start(); await flushPromises(); - expect(mockMQTT.publish).not.toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).not.toHaveBeenCalledWith( 'zigbee2mqtt/bulb', `{"state":"ON","brightness":50,"color_temp":370,"linkquality":99}`, {qos: 0, retain: true}, - expect.any(Function), ); - expect(mockMQTT.publish).not.toHaveBeenCalledWith('zigbee2mqtt/remote', `{"brightness":255}`, {qos: 0, retain: true}, expect.any(Function)); + expect(mockMQTT.publishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/remote', `{"brightness":255}`, {qos: 0, retain: true}); }); it('Log when MQTT client is unavailable', async () => { @@ -278,16 +280,12 @@ describe('Controller', () => { expect(mockExit).toHaveBeenCalledTimes(1); }); - it('Start controller fails due to MQTT', async () => { - const cb = (type: string, handler: EventHandler): void => { - if (type === 'error') { - handler({message: 'addr not found'}); - } - }; - mockMQTT.on.mockImplementationOnce(cb).mockImplementationOnce(cb); + it('Start controller fails due to MQTT connect error', async () => { + mockMQTTConnectAsync.mockImplementationOnce(() => { + throw new Error('addr not found'); + }); await controller.start(); await flushPromises(); - expect(mockLogger.error).toHaveBeenCalledWith('MQTT error: addr not found'); expect(mockLogger.error).toHaveBeenCalledWith('MQTT failed to connect, exiting... (addr not found)'); expect(mockExit).toHaveBeenCalledTimes(1); expect(mockExit).toHaveBeenCalledWith(1, false); @@ -296,7 +294,7 @@ describe('Controller', () => { it('Start controller and stop with restart', async () => { await controller.start(); await controller.stop(true); - expect(mockMQTT.end).toHaveBeenCalledTimes(1); + expect(mockMQTT.endAsync).toHaveBeenCalledTimes(1); expect(mockZHController.stop).toHaveBeenCalledTimes(1); expect(mockExit).toHaveBeenCalledTimes(1); expect(mockExit).toHaveBeenCalledWith(0, true); @@ -306,7 +304,7 @@ describe('Controller', () => { mockZHController.stop.mockRejectedValueOnce('failed'); await controller.start(); await controller.stop(); - expect(mockMQTT.end).toHaveBeenCalledTimes(1); + expect(mockMQTT.endAsync).toHaveBeenCalledTimes(1); expect(mockZHController.stop).toHaveBeenCalledTimes(1); expect(mockExit).toHaveBeenCalledTimes(1); expect(mockExit).toHaveBeenCalledWith(1, false); @@ -317,12 +315,45 @@ describe('Controller', () => { await controller.start(); await mockZHEvents.adapterDisconnected(); await flushPromises(); - expect(mockMQTT.end).toHaveBeenCalledTimes(1); + expect(mockMQTT.endAsync).toHaveBeenCalledTimes(1); expect(mockZHController.stop).toHaveBeenCalledTimes(1); expect(mockExit).toHaveBeenCalledTimes(1); expect(mockExit).toHaveBeenCalledWith(1, false); }); + it('Handles reconnecting to MQTT', async () => { + await controller.start(); + await flushPromises(); + + mockLogger.error.mockClear(); + mockLogger.info.mockClear(); + + mockMQTTEvents.error(new Error('ECONNRESET')); + mockMQTT.reconnecting = true; + expect(mockLogger.error).toHaveBeenCalledWith('MQTT error: ECONNRESET'); + + await jest.advanceTimersByTimeAsync(11000); + expect(mockLogger.error).toHaveBeenCalledWith('Not connected to MQTT server!'); + + mockMQTT.reconnecting = false; + await mockMQTTEvents.connect(); + expect(mockLogger.info).toHaveBeenCalledWith('Connected to MQTT server'); + }); + + it('Handles MQTT publish error', async () => { + await controller.start(); + await flushPromises(); + mockMQTT.publishAsync.mockClear(); + // fail on device_joined (has skipLog=false) + mockMQTT.publishAsync.mockImplementationOnce(mockMQTT.publishAsync.getMockImplementation()!).mockImplementationOnce(() => { + throw new Error('ECONNRESET'); + }); + await mockZHEvents.deviceJoined({device: devices.bulb}); + await flushPromises(); + + expect(mockLogger.error).toHaveBeenCalledWith('MQTT server error: ECONNRESET'); + }); + it('Handle mqtt message', async () => { // @ts-expect-error private const spyEventbusEmitMQTTMessage = jest.spyOn(controller.eventBus, 'emitMQTTMessage').mockImplementation(); @@ -402,11 +433,10 @@ describe('Controller', () => { const payload = {device}; await mockZHEvents.deviceJoined(payload); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', stringify({type: 'device_joined', data: {friendly_name: 'bulb', ieee_address: device.ieeeAddr}}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -465,11 +495,10 @@ describe('Controller', () => { mockZHEvents.deviceJoined(payload); mockZHEvents.deviceJoined(payload); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', stringify({type: 'device_joined', data: {friendly_name: 'bulb', ieee_address: device.ieeeAddr}}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -479,11 +508,10 @@ describe('Controller', () => { const payload = {device, status: 'started'}; await mockZHEvents.deviceInterview(payload); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', stringify({type: 'device_interview', data: {friendly_name: 'bulb', status: 'started', ieee_address: device.ieeeAddr}}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -493,23 +521,22 @@ describe('Controller', () => { const payload = {device, status: 'failed'}; await mockZHEvents.deviceInterview(payload); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', stringify({type: 'device_interview', data: {friendly_name: 'bulb', status: 'failed', ieee_address: device.ieeeAddr}}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('On zigbee deviceInterview successful supported', async () => { await controller.start(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); const device = devices.bulb; const payload = {device, status: 'successful'}; await mockZHEvents.deviceInterview(payload); await flushPromises(); - expect(mockMQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bridge/event'); - const parsedMessage = JSON.parse(mockMQTT.publish.mock.calls[1][1]); + expect(mockMQTT.publishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bridge/event'); + const parsedMessage = JSON.parse(mockMQTT.publishAsync.mock.calls[1][1]); expect(parsedMessage.type).toStrictEqual('device_interview'); expect(parsedMessage.data.friendly_name).toStrictEqual('bulb'); expect(parsedMessage.data.status).toStrictEqual('successful'); @@ -520,18 +547,18 @@ describe('Controller', () => { expect(parsedMessage.data.definition.description).toStrictEqual('TRADFRI bulb E26/E27, white spectrum, globe, opal, 980 lm'); expect(parsedMessage.data.definition.exposes).toStrictEqual(expect.any(Array)); expect(parsedMessage.data.definition.options).toStrictEqual(expect.any(Array)); - expect(mockMQTT.publish.mock.calls[1][2]).toStrictEqual({retain: false, qos: 0}); + expect(mockMQTT.publishAsync.mock.calls[1][2]).toStrictEqual({retain: false, qos: 0}); }); it('On zigbee deviceInterview successful not supported', async () => { await controller.start(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); const device = devices.unsupported; const payload = {device, status: 'successful'}; await mockZHEvents.deviceInterview(payload); await flushPromises(); - expect(mockMQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bridge/event'); - const parsedMessage = JSON.parse(mockMQTT.publish.mock.calls[1][1]); + expect(mockMQTT.publishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/bridge/event'); + const parsedMessage = JSON.parse(mockMQTT.publishAsync.mock.calls[1][1]); expect(parsedMessage.type).toStrictEqual('device_interview'); expect(parsedMessage.data.friendly_name).toStrictEqual(device.ieeeAddr); expect(parsedMessage.data.status).toStrictEqual('successful'); @@ -542,7 +569,7 @@ describe('Controller', () => { expect(parsedMessage.data.definition.description).toStrictEqual('Automatically generated definition'); expect(parsedMessage.data.definition.exposes).toStrictEqual(expect.any(Array)); expect(parsedMessage.data.definition.options).toStrictEqual(expect.any(Array)); - expect(mockMQTT.publish.mock.calls[1][2]).toStrictEqual({retain: false, qos: 0}); + expect(mockMQTT.publishAsync.mock.calls[1][2]).toStrictEqual({retain: false, qos: 0}); }); it('On zigbee event device announce', async () => { @@ -552,11 +579,10 @@ describe('Controller', () => { await mockZHEvents.deviceAnnounce(payload); await flushPromises(); expect(mockLogger.debug).toHaveBeenCalledWith(`Device 'bulb' announced itself`); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', stringify({type: 'device_announce', data: {friendly_name: 'bulb', ieee_address: device.ieeeAddr}}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -564,16 +590,15 @@ describe('Controller', () => { await controller.start(); returnDevices.push('0x00124b00120144ae'); settings.set(['devices'], {}); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); const device = devices.bulb; const payload = {ieeeAddr: device.ieeeAddr}; await mockZHEvents.deviceLeave(payload); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', stringify({type: 'device_leave', data: {ieee_address: device.ieeeAddr, friendly_name: device.ieeeAddr}}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -581,22 +606,21 @@ describe('Controller', () => { await controller.start(); returnDevices.push('0x00124b00120144ae'); const device = devices.bulb; - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); const payload = {ieeeAddr: device.ieeeAddr}; await mockZHEvents.deviceLeave(payload); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', stringify({type: 'device_leave', data: {ieee_address: device.ieeeAddr, friendly_name: 'bulb'}}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Publish entity state attribute output', async () => { await controller.start(); settings.set(['experimental', 'output'], 'attribute'); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); // @ts-expect-error private const device = controller.zigbee.resolveEntity('bulb')!; await controller.publishEntityState(device, { @@ -609,34 +633,33 @@ describe('Controller', () => { brightness: 50, }); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/state', 'ON', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/brightness', '50', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/color_temp', '370', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/color', '100,50,10', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/dummy-1', 'yes', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/dummy-2', 'no', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/test1', '', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/test', '', {qos: 0, retain: true}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/state', 'ON', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/brightness', '50', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/color_temp', '370', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/color', '100,50,10', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/dummy-1', 'yes', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/dummy-2', 'no', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/test1', '', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/test', '', {qos: 0, retain: true}); }); it('Publish entity state attribute_json output', async () => { await controller.start(); settings.set(['experimental', 'output'], 'attribute_and_json'); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); // @ts-expect-error private const device = controller.zigbee.resolveEntity('bulb')!; await controller.publishEntityState(device, {state: 'ON', brightness: 200, color_temp: 370, linkquality: 99}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(5); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/state', 'ON', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/brightness', '200', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/color_temp', '370', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/linkquality', '99', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(5); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/state', 'ON', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/brightness', '200', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/color_temp', '370', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/linkquality', '99', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', stringify({state: 'ON', brightness: 200, color_temp: 370, linkquality: 99}), {qos: 0, retain: true}, - expect.any(Function), ); }); @@ -644,19 +667,18 @@ describe('Controller', () => { await controller.start(); settings.set(['experimental', 'output'], 'attribute_and_json'); settings.set(['devices', devices.bulb.ieeeAddr, 'filtered_attributes'], ['color_temp', 'linkquality']); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); // @ts-expect-error private const device = controller.zigbee.resolveEntity('bulb')!; await controller.publishEntityState(device, {state: 'ON', brightness: 200, color_temp: 370, linkquality: 99}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(3); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/state', 'ON', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/brightness', '200', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/state', 'ON', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/brightness', '200', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', stringify({state: 'ON', brightness: 200}), {qos: 0, retain: true}, - expect.any(Function), ); }); @@ -664,19 +686,18 @@ describe('Controller', () => { await controller.start(); settings.set(['experimental', 'output'], 'attribute_and_json'); settings.set(['device_options', 'filtered_attributes'], ['color_temp', 'linkquality']); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); // @ts-expect-error private const device = controller.zigbee.resolveEntity('bulb')!; await controller.publishEntityState(device, {state: 'ON', brightness: 200, color_temp: 370, linkquality: 99}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(3); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/state', 'ON', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/brightness', '200', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/state', 'ON', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/brightness', '200', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', stringify({state: 'ON', brightness: 200}), {qos: 0, retain: true}, - expect.any(Function), ); }); @@ -684,7 +705,7 @@ describe('Controller', () => { await controller.start(); settings.set(['advanced', 'output'], 'attribute_and_json'); settings.set(['devices', devices.bulb.ieeeAddr, 'filtered_cache'], ['linkquality']); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); // @ts-expect-error private const device = controller.zigbee.resolveEntity('bulb')!; @@ -696,15 +717,14 @@ describe('Controller', () => { // @ts-expect-error private expect(controller.state.state[device.ieeeAddr]).toStrictEqual({brightness: 200, color_temp: 370, state: 'ON'}); - expect(mockMQTT.publish).toHaveBeenCalledTimes(5); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/state', 'ON', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/brightness', '200', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/linkquality', '87', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(5); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/state', 'ON', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/brightness', '200', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/linkquality', '87', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', stringify({state: 'ON', brightness: 200, color_temp: 370, linkquality: 87}), {qos: 0, retain: true}, - expect.any(Function), ); }); @@ -712,7 +732,7 @@ describe('Controller', () => { await controller.start(); settings.set(['advanced', 'output'], 'attribute_and_json'); settings.set(['device_options', 'filtered_cache'], ['linkquality']); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); // @ts-expect-error private const device = controller.zigbee.resolveEntity('bulb')!; @@ -724,27 +744,26 @@ describe('Controller', () => { // @ts-expect-error private expect(controller.state.state[device.ieeeAddr]).toStrictEqual({brightness: 200, color_temp: 370, state: 'ON'}); - expect(mockMQTT.publish).toHaveBeenCalledTimes(5); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/state', 'ON', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/brightness', '200', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb/linkquality', '87', {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(5); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/state', 'ON', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/brightness', '200', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb/linkquality', '87', {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', stringify({state: 'ON', brightness: 200, color_temp: 370, linkquality: 87}), {qos: 0, retain: true}, - expect.any(Function), ); }); it('Publish entity state with device information', async () => { await controller.start(); settings.set(['mqtt', 'include_device_information'], true); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); // @ts-expect-error private let device = controller.zigbee.resolveEntity('bulb')!; await controller.publishEntityState(device, {state: 'ON'}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', stringify({ state: 'ON', @@ -762,7 +781,6 @@ describe('Controller', () => { }, }), {qos: 0, retain: true}, - expect.any(Function), ); // Unsupported device should have model "unknown" @@ -770,7 +788,7 @@ describe('Controller', () => { device = controller.zigbee.resolveEntity('unsupported2')!; await controller.publishEntityState(device, {state: 'ON'}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/unsupported2', stringify({ state: 'ON', @@ -785,39 +803,36 @@ describe('Controller', () => { }, }), {qos: 0, retain: false}, - expect.any(Function), ); }); it('Should publish entity state without retain', async () => { await controller.start(); settings.set(['devices', devices.bulb.ieeeAddr, 'retain'], false); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); // @ts-expect-error private const device = controller.zigbee.resolveEntity('bulb')!; await controller.publishEntityState(device, {state: 'ON'}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', stringify({state: 'ON', brightness: 50, color_temp: 370, linkquality: 99}), {qos: 0, retain: false}, - expect.any(Function), ); }); it('Should publish entity state with retain', async () => { await controller.start(); settings.set(['devices', devices.bulb.ieeeAddr, 'retain'], true); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); // @ts-expect-error private const device = controller.zigbee.resolveEntity('bulb')!; await controller.publishEntityState(device, {state: 'ON'}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', stringify({state: 'ON', brightness: 50, color_temp: 370, linkquality: 99}), {qos: 0, retain: true}, - expect.any(Function), ); }); @@ -826,47 +841,45 @@ describe('Controller', () => { settings.set(['mqtt', 'version'], 5); settings.set(['devices', devices.bulb.ieeeAddr, 'retain'], true); settings.set(['devices', devices.bulb.ieeeAddr, 'retention'], 37); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); // @ts-expect-error private const device = controller.zigbee.resolveEntity('bulb')!; await controller.publishEntityState(device, {state: 'ON'}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', stringify({state: 'ON', brightness: 50, color_temp: 370, linkquality: 99}), {qos: 0, retain: true, properties: {messageExpiryInterval: 37}}, - expect.any(Function), ); }); it('Publish entity state no empty messages', async () => { data.writeEmptyState(); await controller.start(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); // @ts-expect-error private const device = controller.zigbee.resolveEntity('bulb')!; await controller.publishEntityState(device, {}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(0); + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(0); }); it('Should allow to disable state persistency', async () => { settings.set(['advanced', 'cache_state_persistent'], false); data.removeState(); await controller.start(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); // @ts-expect-error private const device = controller.zigbee.resolveEntity('bulb')!; await controller.publishEntityState(device, {state: 'ON'}); await controller.publishEntityState(device, {brightness: 200}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(2); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({state: 'ON'}), {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({state: 'ON'}), {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', stringify({state: 'ON', brightness: 200}), {qos: 0, retain: true}, - expect.any(Function), ); await controller.stop(); expect(data.stateExists()).toBeFalsy(); @@ -887,15 +900,15 @@ describe('Controller', () => { settings.set(['advanced', 'cache_state'], false); data.writeEmptyState(); await controller.start(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); // @ts-expect-error private const device = controller.zigbee.resolveEntity('bulb')!; await controller.publishEntityState(device, {state: 'ON'}); await controller.publishEntityState(device, {brightness: 200}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(2); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({state: 'ON'}), {qos: 0, retain: true}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({brightness: 200}), {qos: 0, retain: true}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({state: 'ON'}), {qos: 0, retain: true}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({brightness: 200}), {qos: 0, retain: true}); }); it('Should start when state is corrupted', async () => { @@ -910,58 +923,63 @@ describe('Controller', () => { settings.set(['mqtt', 'force_disable_retain'], true); await controller.start(); await flushPromises(); - expect(mockMQTTConnect).toHaveBeenCalledTimes(1); + expect(mockMQTTConnectAsync).toHaveBeenCalledTimes(1); const expected = { will: {payload: Buffer.from('{"state":"offline"}'), retain: false, topic: 'zigbee2mqtt/bridge/state', qos: 1}, }; - expect(mockMQTTConnect).toHaveBeenCalledWith('mqtt://localhost', expected); + expect(mockMQTTConnectAsync).toHaveBeenCalledWith('mqtt://localhost', expected); }); - it('Should republish retained messages on MQTT reconnect', async () => { + it('Should republish retained messages on MQTT initial connect', async () => { await controller.start(); - mockMQTT.publish.mockClear(); - mockMQTTEvents['connect'](); + await flushPromises(); + + const retainedMessages = Object.keys( + // @ts-expect-error private + controller.mqtt.retainedMessages, + ).length; + + mockMQTT.publishAsync.mockClear(); await jest.advanceTimersByTimeAsync(2500); // before any startup configure triggers - expect(mockMQTT.publish).toHaveBeenCalledTimes(13); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); + + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(retainedMessages); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), {retain: true, qos: 0}); }); - it('Should not republish retained messages on MQTT reconnect when retained message are sent', async () => { + it('Should not republish retained messages on MQTT initial connect when retained message are sent', async () => { await controller.start(); - mockMQTT.publish.mockClear(); - mockMQTTEvents['connect'](); await flushPromises(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/bridge/info', 'dummy'); await jest.advanceTimersByTimeAsync(2500); // before any startup configure triggers - expect(mockMQTT.publish).toHaveBeenCalledTimes(1); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/state', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); + + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(0); }); it('Should prevent any message being published with retain flag when force_disable_retain is set', async () => { settings.set(['mqtt', 'force_disable_retain'], true); // @ts-expect-error private await controller.mqtt.connect(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); // @ts-expect-error private await controller.mqtt.publish('fo', 'bar', {retain: true}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(1); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/fo', 'bar', {retain: false, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/fo', 'bar', {retain: false, qos: 0}); }); it('Should publish last seen changes', async () => { settings.set(['advanced', 'last_seen'], 'epoch'); await controller.start(); await flushPromises(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); const device = devices.remote; await mockZHEvents.lastSeenChanged({device, reason: 'deviceAnnounce'}); - expect(mockMQTT.publish).toHaveBeenCalledTimes(1); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/remote', stringify({brightness: 255, last_seen: 1000}), {qos: 0, retain: true}, - expect.any(Function), ); }); @@ -969,10 +987,10 @@ describe('Controller', () => { settings.set(['advanced', 'last_seen'], 'epoch'); await controller.start(); await flushPromises(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); const device = devices.remote; await mockZHEvents.lastSeenChanged({device, reason: 'messageEmitted'}); - expect(mockMQTT.publish).toHaveBeenCalledTimes(0); + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(0); }); it('Ignore messages from coordinator', async () => { diff --git a/test/extensions/availability.test.ts b/test/extensions/availability.test.ts index 43a2395655..3e143b2c7d 100644 --- a/test/extensions/availability.test.ts +++ b/test/extensions/availability.test.ts @@ -13,7 +13,7 @@ import Availability from '../../lib/extension/availability'; import * as settings from '../../lib/util/settings'; import utils from '../../lib/util/utils'; -const mocksClear = [mockMQTT.publish, mockLogger.warning, mockLogger.info]; +const mocksClear = [mockMQTT.publishAsync, mockLogger.warning, mockLogger.info]; returnDevices.push( devices.bulb_color.ieeeAddr, @@ -70,23 +70,20 @@ describe('Extension: Availability', () => { }); it('Should publish availability on startup for device where it is enabled for', async () => { - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color/availability', stringify({state: 'online'}), {retain: true, qos: 1}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/remote/availability', stringify({state: 'online'}), {retain: true, qos: 1}, - expect.any(Function), ); - expect(mockMQTT.publish).not.toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).not.toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color_2/availability', stringify({state: 'online'}), {retain: true, qos: 1}, - expect.any(Function), ); }); @@ -114,18 +111,17 @@ describe('Extension: Availability', () => { }); it('Should publish offline for active device when not seen for 10 minutes', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await setTimeAndAdvanceTimers(utils.minutes(5)); expect(devices.bulb_color.ping).toHaveBeenCalledTimes(0); await setTimeAndAdvanceTimers(utils.minutes(7)); expect(devices.bulb_color.ping).toHaveBeenCalledTimes(1); expect(devices.bulb_color.ping).toHaveBeenNthCalledWith(1, true); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color/availability', stringify({state: 'offline'}), {retain: true, qos: 1}, - expect.any(Function), ); }); @@ -137,29 +133,27 @@ describe('Extension: Availability', () => { }); it('Should publish offline for passive device when not seen for 25 hours', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await setTimeAndAdvanceTimers(utils.hours(26)); expect(devices.remote.ping).toHaveBeenCalledTimes(0); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/remote/availability', stringify({state: 'offline'}), {retain: true, qos: 1}, - expect.any(Function), ); }); it('Should reset ping timer when device last seen changes for active device', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await setTimeAndAdvanceTimers(utils.minutes(5)); expect(devices.bulb_color.ping).toHaveBeenCalledTimes(0); await mockZHEvents.lastSeenChanged({device: devices.bulb_color}); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color/availability', stringify({state: 'offline'}), {retain: true, qos: 1}, - expect.any(Function), ); await setTimeAndAdvanceTimers(utils.minutes(7)); @@ -171,7 +165,7 @@ describe('Extension: Availability', () => { }); it('Should ping again when first ping fails', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockZHEvents.lastSeenChanged({device: devices.bulb_color}); @@ -186,17 +180,16 @@ describe('Extension: Availability', () => { }); it('Should reset ping timer when device last seen changes for passive device', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await setTimeAndAdvanceTimers(utils.hours(24)); expect(devices.remote.ping).toHaveBeenCalledTimes(0); await mockZHEvents.lastSeenChanged({device: devices.remote}); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/remote/availability', stringify({state: 'offline'}), {retain: true, qos: 1}, - expect.any(Function), ); await setTimeAndAdvanceTimers(utils.hours(25)); @@ -207,24 +200,22 @@ describe('Extension: Availability', () => { }); it('Should immediately mark device as online when it lastSeen changes', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await setTimeAndAdvanceTimers(utils.minutes(15)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color/availability', stringify({state: 'offline'}), {retain: true, qos: 1}, - expect.any(Function), ); devices.bulb_color.lastSeen = Date.now(); await mockZHEvents.lastSeenChanged({device: devices.bulb_color}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color/availability', stringify({state: 'online'}), {retain: true, qos: 1}, - expect.any(Function), ); }); @@ -336,38 +327,35 @@ describe('Extension: Availability', () => { }); it('Should republish availability when device is renamed', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/rename', stringify({from: 'bulb_color', to: 'bulb_new_name'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb_color/availability', '', {retain: true, qos: 1}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color/availability', '', {retain: true, qos: 1}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_new_name/availability', stringify({state: 'online'}), {retain: true, qos: 1}, - expect.any(Function), ); await setTimeAndAdvanceTimers(utils.hours(12)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_new_name/availability', stringify({state: 'offline'}), {retain: true, qos: 1}, - expect.any(Function), ); }); it('Should publish availability payload in JSON format', async () => { await resetExtension(); devices.remote.ping.mockClear(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await setTimeAndAdvanceTimers(utils.hours(26)); expect(devices.remote.ping).toHaveBeenCalledTimes(0); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/remote/availability', stringify({state: 'offline'}), {retain: true, qos: 1}, - expect.any(Function), ); }); @@ -376,29 +364,26 @@ describe('Extension: Availability', () => { await resetExtension(); devices.bulb_color_2.ping.mockClear(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/group_tradfri_remote/availability', stringify({state: 'online'}), {retain: true, qos: 1}, - expect.any(Function), ); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await setTimeAndAdvanceTimers(utils.minutes(12)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/group_tradfri_remote/availability', stringify({state: 'offline'}), {retain: true, qos: 1}, - expect.any(Function), ); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); devices.bulb_color_2.lastSeen = Date.now(); await mockZHEvents.lastSeenChanged({device: devices.bulb_color_2}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/group_tradfri_remote/availability', stringify({state: 'online'}), {retain: true, qos: 1}, - expect.any(Function), ); }); diff --git a/test/extensions/bind.test.ts b/test/extensions/bind.test.ts index e8ec68c6b1..13980129d8 100644 --- a/test/extensions/bind.test.ts +++ b/test/extensions/bind.test.ts @@ -12,7 +12,7 @@ import * as settings from '../../lib/util/settings'; const mocksClear = [ mockDebounce, - mockMQTT.publish, + mockMQTT.publishAsync, devices.bulb_color.getEndpoint(1)!.configureReporting, devices.bulb_color.getEndpoint(1)!.bind, devices.bulb_color_2.getEndpoint(1)!.read, @@ -92,7 +92,7 @@ describe('Extension: Bind', () => { {attribute: 'currentX', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1}, {attribute: 'currentY', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1}, ]); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({ transaction: '1234', @@ -106,10 +106,9 @@ describe('Extension: Bind', () => { status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}); // Teardown target.binds = originalTargetBinds; @@ -117,14 +116,13 @@ describe('Extension: Bind', () => { }); it('Should throw error on invalid payload', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/bind', stringify({fromz: 'remote', to: 'bulb_color'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({data: {}, status: 'error', error: 'Invalid payload'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -170,7 +168,7 @@ describe('Extension: Bind', () => { {attribute: 'currentX', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1}, {attribute: 'currentY', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1}, ]); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({ transaction: '1234', @@ -184,10 +182,9 @@ describe('Extension: Bind', () => { status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}); // Teardown target.binds = originalTargetBinds; @@ -241,7 +238,7 @@ describe('Extension: Bind', () => { {attribute: 'currentX', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1}, {attribute: 'currentY', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1}, ]); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({ transaction: '1234', @@ -255,10 +252,9 @@ describe('Extension: Bind', () => { status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}); // Teardown target.configuredReportings = originalTargetCR; @@ -275,11 +271,10 @@ describe('Extension: Bind', () => { await flushPromises(); expect(endpoint.bind).toHaveBeenCalledTimes(1); expect(endpoint.bind).toHaveBeenCalledWith('genOnOff', target); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({data: {from: 'remote', from_endpoint: 'default', to: 'bulb_color', clusters: ['genOnOff'], failed: []}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -291,11 +286,10 @@ describe('Extension: Bind', () => { mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'button'})); await flushPromises(); expect(endpoint.bind).toHaveBeenCalledTimes(0); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({data: {}, status: 'error', error: 'Nothing to bind'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -341,14 +335,13 @@ describe('Extension: Bind', () => { {attribute: 'currentY', minimumReportInterval: 5, maximumReportInterval: 0xffff, reportableChange: 1}, ]); expect(devices.bulb_color.meta.configured).toBe(332242049); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/unbind', stringify({ data: {from: 'remote', from_endpoint: 'default', to: 'bulb_color', clusters: ['genScenes', 'genOnOff', 'genLevelCtrl'], failed: []}, status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); // Teardown @@ -368,14 +361,13 @@ describe('Extension: Bind', () => { expect(endpoint.unbind).toHaveBeenCalledWith('genOnOff', target); expect(endpoint.unbind).toHaveBeenCalledWith('genLevelCtrl', target); expect(endpoint.unbind).toHaveBeenCalledWith('genScenes', target); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/unbind', stringify({ data: {from: 'remote', from_endpoint: 'default', to: 'Coordinator', clusters: ['genScenes', 'genOnOff', 'genLevelCtrl'], failed: []}, status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -400,14 +392,13 @@ describe('Extension: Bind', () => { expect(target1Member.configureReporting).toHaveBeenCalledWith('genLevelCtrl', [ {attribute: 'currentLevel', maximumReportInterval: 3600, minimumReportInterval: 5, reportableChange: 1}, ]); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({ data: {from: 'remote', from_endpoint: 'default', to: 'group_1', clusters: ['genScenes', 'genOnOff', 'genLevelCtrl'], failed: []}, status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); // Should configure reporting for device added to group @@ -437,14 +428,13 @@ describe('Extension: Bind', () => { expect(endpoint.unbind).toHaveBeenCalledWith('genOnOff', target); expect(endpoint.unbind).toHaveBeenCalledWith('genLevelCtrl', target); expect(endpoint.unbind).toHaveBeenCalledWith('genScenes', target); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/unbind', stringify({ data: {from: 'remote', from_endpoint: 'default', to: 'group_1', clusters: ['genScenes', 'genOnOff', 'genLevelCtrl'], failed: []}, status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -515,14 +505,13 @@ describe('Extension: Bind', () => { expect(endpoint.bind).toHaveBeenCalledWith('genOnOff', target); expect(endpoint.bind).toHaveBeenCalledWith('genLevelCtrl', target); expect(endpoint.bind).toHaveBeenCalledWith('genScenes', target); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({ data: {from: 'remote', from_endpoint: 'default', to: '1', clusters: ['genScenes', 'genOnOff', 'genLevelCtrl'], failed: []}, status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -537,11 +526,10 @@ describe('Extension: Bind', () => { mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'bulb_color'})); await flushPromises(); expect(endpoint.bind).toHaveBeenCalledTimes(3); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({data: {}, status: 'error', error: 'Failed to bind'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -557,14 +545,13 @@ describe('Extension: Bind', () => { await flushPromises(); expect(endpoint.bind).toHaveBeenCalledTimes(1); expect(endpoint.bind).toHaveBeenCalledWith('genOnOff', target); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({ data: {from: 'remote', from_endpoint: 'ep2', to: 'wall_switch_double', to_endpoint: 'right', clusters: ['genOnOff'], failed: []}, status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -580,14 +567,13 @@ describe('Extension: Bind', () => { await flushPromises(); expect(endpoint.bind).toHaveBeenCalledTimes(1); expect(endpoint.bind).toHaveBeenCalledWith('genOnOff', target); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({ data: {from: 'remote', from_endpoint: 2, to: 'wall_switch_double', to_endpoint: 3, clusters: ['genOnOff'], failed: []}, status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -600,7 +586,7 @@ describe('Extension: Bind', () => { await flushPromises(); expect(endpoint.bind).toHaveBeenCalledTimes(1); expect(endpoint.bind).toHaveBeenCalledWith('msTemperatureMeasurement', target); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({ data: { @@ -613,7 +599,6 @@ describe('Extension: Bind', () => { status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -626,11 +611,10 @@ describe('Extension: Bind', () => { await flushPromises(); expect(endpoint.bind).toHaveBeenCalledTimes(1); expect(endpoint.bind).toHaveBeenCalledWith('genOnOff', target); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({data: {from: 'remote', from_endpoint: 'ep2', to: 'wall_switch', clusters: ['genOnOff'], failed: []}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -645,7 +629,7 @@ describe('Extension: Bind', () => { expect(endpoint.unbind).toHaveBeenCalledWith('genOnOff', 901); expect(endpoint.unbind).toHaveBeenCalledWith('genLevelCtrl', 901); expect(endpoint.unbind).toHaveBeenCalledWith('genScenes', 901); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/unbind', stringify({ data: { @@ -658,7 +642,6 @@ describe('Extension: Bind', () => { status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -667,11 +650,10 @@ describe('Extension: Bind', () => { mockClear(device); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote_not_existing', to: 'bulb_color'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({data: {}, status: 'error', error: "Source device 'remote_not_existing' does not exist"}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -683,11 +665,10 @@ describe('Extension: Bind', () => { stringify({from: 'remote', from_endpoint: 'not_existing_endpoint', to: 'bulb_color'}), ); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({data: {}, status: 'error', error: "Source device 'remote' does not have endpoint 'not_existing_endpoint'"}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -696,11 +677,10 @@ describe('Extension: Bind', () => { mockClear(device); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'bulb_color_not_existing'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({data: {}, status: 'error', error: "Target device or group 'bulb_color_not_existing' does not exist"}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -712,11 +692,10 @@ describe('Extension: Bind', () => { stringify({from: 'remote', to: 'bulb_color', to_endpoint: 'not_existing_endpoint'}), ); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', stringify({data: {}, status: 'error', error: "Target device 'bulb_color' does not have endpoint 'not_existing_endpoint'"}), {retain: false, qos: 0}, - expect.any(Function), ); }); diff --git a/test/extensions/bridge.test.ts b/test/extensions/bridge.test.ts index 3db3810fd6..5757712485 100644 --- a/test/extensions/bridge.test.ts +++ b/test/extensions/bridge.test.ts @@ -31,7 +31,7 @@ returnDevices.push(devices.bulb_custom_cluster.ieeeAddr); const mocksClear = [ mockLogger.info, mockLogger.warning, - mockMQTT.publish, + mockMQTT.publishAsync, mockZHController.permitJoin, devices.bulb.interview, devices.bulb.removeFromDatabase, @@ -86,7 +86,7 @@ describe('Extension: Bridge', () => { const zhcVersion = await utils.getDependencyVersion('zigbee-herdsman-converters'); const directory = settings.get().advanced.log_directory; // console.log(mockMQTT.publish.mock.calls.find((c) => c[0] === 'zigbee2mqtt/bridge/info')[1]) - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/info', stringify({ commit: version.commitHash, @@ -218,14 +218,13 @@ describe('Extension: Bridge', () => { zigbee_herdsman_converters: zhcVersion, }), {retain: true, qos: 0}, - expect.any(Function), ); }); it('Should publish devices on startup', async () => { await resetExtension(); // console.log(mockMQTT.publish.mock.calls.find((c) => c[0] === 'zigbee2mqtt/bridge/devices')[1]); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/devices', stringify([ { @@ -2077,38 +2076,35 @@ describe('Extension: Bridge', () => { }, ]), {retain: true, qos: 0}, - expect.any(Function), ); }); it('Should publish definitions on startup', async () => { await resetExtension(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/definitions', expect.stringContaining(stringify(CUSTOM_CLUSTERS)), {retain: true, qos: 0}, - expect.any(Function), ); }); it('Should log to MQTT', async () => { mockLogger.setTransportsEnabled(true); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockLogger.info.mockClear(); mockLogger.info('this is a test'); mockLogger.info('this is a test'); // Should not publish dupes - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/logging', stringify({message: 'this is a test', level: 'info', namespace: 'z2m'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledTimes(1); + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1); // Should not publish debug logging - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockLogger.debug('this is a test'); - expect(mockMQTT.publish).toHaveBeenCalledTimes(0); + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(0); }); it('Should log to MQTT including debug when enabled', async () => { @@ -2116,22 +2112,21 @@ describe('Extension: Bridge', () => { await resetExtension(); mockLogger.setTransportsEnabled(true); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockLogger.info.mockClear(); mockLogger.info('this is a test'); mockLogger.info('this is a test'); // Should not publish dupes - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/logging', stringify({message: 'this is a test', level: 'info', namespace: 'z2m'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledTimes(1); + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1); // Should publish debug logging - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockLogger.debug('this is a test'); - expect(mockMQTT.publish).toHaveBeenCalledTimes(1); + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1); settings.set(['advanced', 'log_debug_to_mqtt_frontend'], false); settings.reRead(); @@ -2140,11 +2135,11 @@ describe('Extension: Bridge', () => { it('Shouldnt log to MQTT when not connected', async () => { mockLogger.setTransportsEnabled(true); mockMQTT.reconnecting = true; - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockLogger.info.mockClear(); mockLogger.error.mockClear(); mockLogger.info('this is a test'); - expect(mockMQTT.publish).toHaveBeenCalledTimes(0); + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(0); expect(mockLogger.info).toHaveBeenCalledTimes(1); expect(mockLogger.error).toHaveBeenCalledTimes(0); }); @@ -2153,7 +2148,7 @@ describe('Extension: Bridge', () => { await resetExtension(); mockLogger.setTransportsEnabled(true); // console.log(MQTT.publish.mock.calls.filter((c) => c[0] === 'zigbee2mqtt/bridge/groups')); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/groups', stringify([ {friendly_name: 'group_1', id: 1, members: [], scenes: []}, @@ -2193,77 +2188,72 @@ describe('Extension: Bridge', () => { }, ]), {retain: true, qos: 0}, - expect.any(Function), ); }); it('Should publish event when device joined', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockZHEvents.deviceJoined({device: devices.bulb}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', stringify({type: 'device_joined', data: {friendly_name: 'bulb', ieee_address: '0x000b57fffec6a5b2'}}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should publish devices when device joined', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockZHEvents.deviceNetworkAddressChanged({device: devices.bulb}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}); }); it('Should publish event when device announces', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockZHEvents.deviceAnnounce({device: devices.bulb}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(2); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', stringify({type: 'device_announce', data: {friendly_name: 'bulb', ieee_address: '0x000b57fffec6a5b2'}}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should publish event when device interview started', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockZHEvents.deviceInterview({device: devices.bulb, status: 'started'}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(2); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', stringify({type: 'device_interview', data: {friendly_name: 'bulb', status: 'started', ieee_address: '0x000b57fffec6a5b2'}}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should publish event and devices when device interview failed', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockZHEvents.deviceInterview({device: devices.bulb, status: 'failed'}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(2); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', stringify({type: 'device_interview', data: {friendly_name: 'bulb', status: 'failed', ieee_address: '0x000b57fffec6a5b2'}}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}); }); it('Should publish event and devices when device interview successful', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockZHEvents.deviceInterview({device: devices.bulb, status: 'successful'}); await mockZHEvents.deviceInterview({device: devices.unsupported, status: 'successful'}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(7); + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(7); // console.log(mockMQTT.publish.mock.calls.filter((c) => c[0] === 'zigbee2mqtt/bridge/event')); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', stringify({ data: { @@ -2486,9 +2476,8 @@ describe('Extension: Bridge', () => { type: 'device_interview', }), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', stringify({ data: { @@ -2572,35 +2561,31 @@ describe('Extension: Bridge', () => { type: 'device_interview', }), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/definitions', expect.any(String), {retain: true, qos: 0}, - expect.any(Function), ); }); it('Should publish event and devices when device leaves', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockZHEvents.deviceLeave({ieeeAddr: devices.bulb.ieeeAddr}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(3); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', stringify({type: 'device_leave', data: {ieee_address: '0x000b57fffec6a5b2', friendly_name: 'bulb'}}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( // Defintitions should be updated on device event 'zigbee2mqtt/bridge/definitions', expect.any(String), {retain: true, qos: 0}, - expect.any(Function), ); }); @@ -2609,11 +2594,10 @@ describe('Extension: Bridge', () => { await flushPromises(); expect(mockZHController.permitJoin).toHaveBeenCalledTimes(1); expect(mockZHController.permitJoin).toHaveBeenCalledWith(1, undefined); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', stringify({data: {time: 1}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -2622,11 +2606,10 @@ describe('Extension: Bridge', () => { await flushPromises(); expect(mockZHController.permitJoin).toHaveBeenCalledTimes(1); expect(mockZHController.permitJoin).toHaveBeenCalledWith(0, undefined); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', stringify({data: {time: 0}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -2635,11 +2618,10 @@ describe('Extension: Bridge', () => { await flushPromises(); expect(mockZHController.permitJoin).toHaveBeenCalledTimes(1); expect(mockZHController.permitJoin).toHaveBeenCalledWith(1, undefined); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', stringify({data: {time: 1}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -2647,71 +2629,66 @@ describe('Extension: Bridge', () => { mockMQTTEvents.message('zigbee2mqtt/bridge/request/permit_join', stringify({time_bla: false})); await flushPromises(); expect(mockZHController.permitJoin).toHaveBeenCalledTimes(0); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', stringify({data: {}, status: 'error', error: 'Invalid payload'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should republish bridge info when permit join changes', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockZHEvents.permitJoinChanged({permitted: false, timeout: 10}); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), {retain: true, qos: 0}); }); it('Shouldnt republish bridge info when permit join changes and hersman is stopping', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockZHController.isStopping.mockImplementationOnce(() => true); await mockZHEvents.permitJoinChanged({permitted: false, timeout: 10}); await flushPromises(); - expect(mockMQTT.publish).not.toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).not.toHaveBeenCalledWith( 'zigbee2mqtt/bridge/info', expect.any(String), {retain: true, qos: 0}, - expect.any(Function), ); }); it('Should allow permit join via device', async () => { const device = devices.bulb; - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/permit_join', stringify({time: 123, device: 'bulb'})); await flushPromises(); expect(mockZHController.permitJoin).toHaveBeenCalledTimes(1); expect(mockZHController.permitJoin).toHaveBeenCalledWith(123, device); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', stringify({data: {time: 123, device: 'bulb'}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should not allow permit join via non-existing device', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/permit_join', stringify({time: 123, device: 'bulb_not_existing_woeeee'})); await flushPromises(); expect(mockZHController.permitJoin).toHaveBeenCalledTimes(0); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', stringify({data: {}, status: 'error', error: "Device 'bulb_not_existing_woeeee' does not exist"}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should put transaction in response when request is done with transaction', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/permit_join', stringify({time: 0, transaction: 22})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', stringify({data: {time: 0}, status: 'ok', transaction: 22}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -2719,31 +2696,29 @@ describe('Extension: Bridge', () => { mockZHController.permitJoin.mockImplementationOnce(() => { throw new Error('Failed to connect to adapter'); }); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/permit_join', stringify({time: 0})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', stringify({data: {}, status: 'error', error: 'Failed to connect to adapter'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should put error in response when format is incorrect', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/options', stringify({options: false})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/options', stringify({data: {}, status: 'error', error: 'Invalid payload'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Coverage satisfaction', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/random', stringify({value: false})); const device = devices.bulb; await mockZHEvents.message({ @@ -2758,33 +2733,31 @@ describe('Extension: Bridge', () => { }); it('Should allow a healthcheck', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/health_check', ''); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/health_check', stringify({data: {healthy: true}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow a coordinator check', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockZHController.coordinatorCheck.mockReturnValueOnce({missingRouters: [mockZHController.getDeviceByIeeeAddr('0x000b57fffec6a5b2')]}); mockMQTTEvents.message('zigbee2mqtt/bridge/request/coordinator_check', ''); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/coordinator_check', stringify({data: {missing_routers: [{friendly_name: 'bulb', ieee_address: '0x000b57fffec6a5b2'}]}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow to remove device by string', async () => { const device = devices.bulb; - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/remove', 'bulb'); await flushPromises(); // @ts-expect-error private @@ -2792,99 +2765,93 @@ describe('Extension: Bridge', () => { expect(device.removeFromNetwork).toHaveBeenCalledTimes(1); expect(device.removeFromDatabase).not.toHaveBeenCalled(); expect(settings.getDevice('bulb')).toBeUndefined(); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', '', {retain: true, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', '', {retain: true, qos: 0}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/remove', stringify({data: {id: 'bulb', block: false, force: false}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); expect(settings.get().blocklist).toStrictEqual([]); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); }); it('Should allow to remove device by object ID', async () => { const device = devices.bulb; - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/remove', stringify({id: 'bulb'})); await flushPromises(); expect(device.removeFromNetwork).toHaveBeenCalledTimes(1); expect(device.removeFromDatabase).not.toHaveBeenCalled(); expect(settings.getDevice('bulb')).toBeUndefined(); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/remove', stringify({data: {id: 'bulb', block: false, force: false}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow to force remove device', async () => { const device = devices.bulb; - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/remove', stringify({id: 'bulb', force: true})); await flushPromises(); expect(device.removeFromDatabase).toHaveBeenCalledTimes(1); expect(device.removeFromNetwork).not.toHaveBeenCalled(); expect(settings.getDevice('bulb')).toBeUndefined(); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/remove', stringify({data: {id: 'bulb', block: false, force: true}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow to block device', async () => { const device = devices.bulb; - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/remove', stringify({id: 'bulb', block: true, force: true})); await flushPromises(); expect(device.removeFromDatabase).toHaveBeenCalledTimes(1); expect(settings.getDevice('bulb')).toBeUndefined(); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/remove', stringify({data: {id: 'bulb', block: true, force: true}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); expect(settings.get().blocklist).toStrictEqual(['0x000b57fffec6a5b2']); }); it('Should allow to remove group', async () => { const group = groups.group_1; - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/remove', 'group_1'); await flushPromises(); expect(group.removeFromNetwork).toHaveBeenCalledTimes(1); expect(settings.getGroup('group_1')).toBeUndefined(); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/remove', stringify({data: {id: 'group_1', force: false}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow to force remove group', async () => { const group = groups.group_1; - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/remove', stringify({id: 'group_1', force: true})); await flushPromises(); expect(group.removeFromDatabase).toHaveBeenCalledTimes(1); expect(settings.getGroup('group_1')).toBeUndefined(); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/remove', stringify({data: {id: 'group_1', force: true}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -2900,35 +2867,33 @@ describe('Extension: Bridge', () => { }); it('Should throw error on removing non-existing device', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/remove', stringify({id: 'non-existing-device'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/remove', stringify({data: {}, status: 'error', error: "Device 'non-existing-device' does not exist"}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should throw error when remove device fails', async () => { const device = devices.bulb; - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); device.removeFromNetwork.mockImplementationOnce(() => { throw new Error('device timeout'); }); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/remove', stringify({id: 'bulb'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/remove', stringify({data: {}, status: 'error', error: "Failed to remove device 'bulb' (block: false, force: false) (Error: device timeout)"}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow rename device', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/rename', stringify({from: 'bulb', to: 'bulb_new_name'})); await flushPromises(); expect(settings.getDevice('bulb')).toBeUndefined(); @@ -2938,75 +2903,69 @@ describe('Extension: Bridge', () => { retain: true, description: 'this is my bulb', }); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', '', {retain: true, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', '', {retain: true, qos: 0}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_new_name', stringify({brightness: 50}), expect.any(Object), - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/rename', stringify({data: {from: 'bulb', to: 'bulb_new_name', homeassistant_rename: false}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Shouldnt allow rename device with to not allowed name containing a wildcard', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/rename', stringify({from: 'bulb', to: 'living_room/blinds#'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/rename', stringify({data: {}, status: 'error', error: "MQTT wildcard (+ and #) not allowed in friendly_name ('living_room/blinds#')"}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow rename group', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/rename', stringify({from: 'group_1', to: 'group_new_name'})); await flushPromises(); expect(settings.getGroup('group_1')).toBeUndefined(); expect(settings.getGroup('group_new_name')).toStrictEqual({ID: 1, friendly_name: 'group_new_name', retain: false}); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/rename', stringify({data: {from: 'group_1', to: 'group_new_name', homeassistant_rename: false}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should throw error on invalid device rename payload', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/rename', stringify({from_bla: 'bulb', to: 'bulb_new_name'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/rename', stringify({data: {}, status: 'error', error: 'Invalid payload'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should throw error on non-existing device rename', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/rename', stringify({from: 'bulb_not_existing', to: 'bulb_new_name'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/rename', stringify({data: {}, status: 'error', error: "Device 'bulb_not_existing' does not exist"}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow to rename last joined device', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockZHEvents.deviceJoined({device: devices.bulb}); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/rename', stringify({last: true, to: 'bulb_new_name'})); await flushPromises(); @@ -3017,54 +2976,50 @@ describe('Extension: Bridge', () => { retain: true, description: 'this is my bulb', }); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/rename', stringify({data: {from: 'bulb', to: 'bulb_new_name', homeassistant_rename: false}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should throw error when renaming device through not allowed friendlyName', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/rename', stringify({from: 'bulb', to: 'bulb_new_name/1'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/rename', stringify({data: {}, status: 'error', error: `Friendly name cannot end with a "/DIGIT" ('bulb_new_name/1')`}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should throw error when renaming last joined device but none has joined', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/rename', stringify({last: true, to: 'bulb_new_name'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/rename', stringify({data: {}, status: 'error', error: 'No device has joined since start'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow interviewing a device by friendly name', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); devices.bulb.interview.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/interview', stringify({id: 'bulb'})); await flushPromises(); expect(devices.bulb.interview).toHaveBeenCalled(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/interview', stringify({data: {id: 'bulb'}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); // The following indicates that devices have published. - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}); }); it('Should allow interviewing a device by ieeeAddr', async () => { @@ -3072,115 +3027,107 @@ describe('Extension: Bridge', () => { const device = controller.zigbee.resolveEntity(devices.bulb)!; assert('resolveDefinition' in device); device.resolveDefinition = jest.fn(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); devices.bulb.interview.mockClear(); expect(device.resolveDefinition).toHaveBeenCalledTimes(0); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/interview', stringify({id: '0x000b57fffec6a5b2'})); await flushPromises(); expect(devices.bulb.interview).toHaveBeenCalledWith(true); expect(device.resolveDefinition).toHaveBeenCalledWith(true); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/interview', stringify({data: {id: '0x000b57fffec6a5b2'}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); // The following indicates that devices have published. - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}); }); it('Should throw error on invalid device interview payload', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/interview', stringify({foo: 'bulb'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/interview', stringify({data: {}, status: 'error', error: 'Invalid payload'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should throw error on non-existing device interview', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/interview', stringify({id: 'bulb_not_existing'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/interview', stringify({data: {}, status: 'error', error: "Device 'bulb_not_existing' does not exist"}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should throw error on id is device endpoint', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/interview', stringify({id: 'bulb/1'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/interview', stringify({data: {}, status: 'error', error: "Device 'bulb/1' does not exist"}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should throw error on id is a group', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/interview', stringify({id: 'group_1'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/interview', stringify({data: {}, status: 'error', error: "Device 'group_1' does not exist"}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should throw error on when interview fails', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); devices.bulb.interview.mockClear(); devices.bulb.interview.mockImplementation(() => Promise.reject(new Error('something went wrong'))); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/interview', stringify({id: 'bulb'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/interview', stringify({data: {}, status: 'error', error: "interview of 'bulb' (0x000b57fffec6a5b2) failed: Error: something went wrong"}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should error when generate_external_definition is invalid', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/generate_external_definition', stringify({wrong: devices.ZNCZ02LM.ieeeAddr})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/generate_external_definition', stringify({data: {}, error: 'Invalid payload', status: 'error'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should error when generate_external_definition requested for unknown device', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/generate_external_definition', stringify({id: 'non_existing_device'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/generate_external_definition', stringify({data: {}, error: "Device 'non_existing_device' does not exist", status: 'error'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow to generate device definition', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/generate_external_definition', stringify({id: devices.ZNCZ02LM.ieeeAddr})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/generate_external_definition', stringify({ data: { @@ -3202,12 +3149,11 @@ describe('Extension: Bridge', () => { status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow change device options', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); expect(settings.getDevice('bulb')).toStrictEqual({ ID: '0x000b57fffec6a5b2', friendly_name: 'bulb', @@ -3223,7 +3169,7 @@ describe('Extension: Bridge', () => { transition: 1, description: 'this is my bulb', }); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/options', stringify({ data: { @@ -3235,12 +3181,11 @@ describe('Extension: Bridge', () => { status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow to remove device option', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); settings.set(['devices', '0x000b57fffec6a5b2', 'qos'], 1); expect(settings.getDevice('bulb')).toStrictEqual({ ID: '0x000b57fffec6a5b2', @@ -3257,7 +3202,7 @@ describe('Extension: Bridge', () => { retain: true, description: 'this is my bulb', }); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/options', stringify({ data: { @@ -3269,12 +3214,11 @@ describe('Extension: Bridge', () => { status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow change device options with restart required', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); expect(settings.getDevice('bulb')).toStrictEqual({ ID: '0x000b57fffec6a5b2', friendly_name: 'bulb', @@ -3290,7 +3234,7 @@ describe('Extension: Bridge', () => { disabled: true, description: 'this is my bulb', }); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/options', stringify({ data: { @@ -3302,26 +3246,24 @@ describe('Extension: Bridge', () => { status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow change group options', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); expect(settings.getGroup('group_1')).toStrictEqual({ID: 1, friendly_name: 'group_1', retain: false}); mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/options', stringify({options: {retain: true, transition: 1}, id: 'group_1'})); await flushPromises(); expect(settings.getGroup('group_1')).toStrictEqual({ID: 1, friendly_name: 'group_1', retain: true, transition: 1}); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/options', stringify({data: {from: {retain: false}, to: {retain: true, transition: 1}, restart_required: false, id: 'group_1'}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow change group options with restart required', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); expect(settings.getGroup('group_1')).toStrictEqual({ID: 1, friendly_name: 'group_1', retain: false}); mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/options', stringify({options: {off_state: 'all_members_off'}, id: 'group_1'})); await flushPromises(); @@ -3331,114 +3273,106 @@ describe('Extension: Bridge', () => { retain: false, off_state: 'all_members_off', }); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/options', stringify({ data: {from: {retain: false}, to: {retain: false, off_state: 'all_members_off'}, restart_required: true, id: 'group_1'}, status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should throw error on invalid device change options payload', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/options', stringify({options_: {retain: true, transition: 1}, id: 'bulb'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/options', stringify({data: {}, status: 'error', error: 'Invalid payload'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow to add group by string', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/add', 'group_193'); await flushPromises(); expect(settings.getGroup('group_193')).toStrictEqual({ID: 3, friendly_name: 'group_193'}); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/add', stringify({data: {friendly_name: 'group_193', id: 3}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow to add group with ID', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/add', stringify({friendly_name: 'group_193', id: 92})); await flushPromises(); expect(settings.getGroup('group_193')).toStrictEqual({ID: 92, friendly_name: 'group_193'}); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/add', stringify({data: {friendly_name: 'group_193', id: 92}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Shouldnt allow to add group with empty name', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/add', stringify({friendly_name: '', id: 9})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/add', stringify({data: {}, status: 'error', error: 'friendly_name must be at least 1 char long'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should throw error when add with invalid payload', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/add', stringify({friendly_name9: 'group_193'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/add', stringify({data: {}, status: 'error', error: 'Invalid payload'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow to touchlink factory reset (succeeds)', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockZHController.touchlinkFactoryResetFirst.mockClear(); mockZHController.touchlinkFactoryResetFirst.mockReturnValueOnce(true); mockMQTTEvents.message('zigbee2mqtt/bridge/request/touchlink/factory_reset', ''); await flushPromises(); expect(mockZHController.touchlinkFactoryResetFirst).toHaveBeenCalledTimes(1); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/touchlink/factory_reset', stringify({data: {}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow to touchlink factory reset specific device', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockZHController.touchlinkFactoryReset.mockClear(); mockZHController.touchlinkFactoryReset.mockReturnValueOnce(true); mockMQTTEvents.message('zigbee2mqtt/bridge/request/touchlink/factory_reset', stringify({ieee_address: '0x1239', channel: 12})); await flushPromises(); expect(mockZHController.touchlinkFactoryReset).toHaveBeenCalledTimes(1); expect(mockZHController.touchlinkFactoryReset).toHaveBeenCalledWith('0x1239', 12); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/touchlink/factory_reset', stringify({data: {ieee_address: '0x1239', channel: 12}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Add install code', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); // By object mockZHController.addInstallCode.mockClear(); @@ -3446,11 +3380,10 @@ describe('Extension: Bridge', () => { await flushPromises(); expect(mockZHController.addInstallCode).toHaveBeenCalledTimes(1); expect(mockZHController.addInstallCode).toHaveBeenCalledWith('my-code'); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/install_code/add', stringify({data: {value: 'my-code'}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); // By string @@ -3459,74 +3392,69 @@ describe('Extension: Bridge', () => { await flushPromises(); expect(mockZHController.addInstallCode).toHaveBeenCalledTimes(1); expect(mockZHController.addInstallCode).toHaveBeenCalledWith('my-string-code'); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/install_code/add', stringify({data: {value: 'my-code'}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Add install code error', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockZHController.addInstallCode.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/install_code/add', stringify({wrong: 'my-code'})); await flushPromises(); expect(mockZHController.addInstallCode).toHaveBeenCalledTimes(0); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/install_code/add', stringify({data: {}, status: 'error', error: 'Invalid payload'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow to touchlink identify specific device', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockZHController.touchlinkIdentify.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/touchlink/identify', stringify({ieee_address: '0x1239', channel: 12})); await flushPromises(); expect(mockZHController.touchlinkIdentify).toHaveBeenCalledTimes(1); expect(mockZHController.touchlinkIdentify).toHaveBeenCalledWith('0x1239', 12); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/touchlink/identify', stringify({data: {ieee_address: '0x1239', channel: 12}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Touchlink identify fails when payload is invalid', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockZHController.touchlinkIdentify.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/touchlink/identify', stringify({ieee_address: '0x1239'})); await flushPromises(); expect(mockZHController.touchlinkIdentify).toHaveBeenCalledTimes(0); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/touchlink/identify', stringify({data: {}, status: 'error', error: 'Invalid payload'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow to touchlink factory reset (fails)', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockZHController.touchlinkFactoryResetFirst.mockClear(); mockZHController.touchlinkFactoryResetFirst.mockReturnValueOnce(false); mockMQTTEvents.message('zigbee2mqtt/bridge/request/touchlink/factory_reset', ''); await flushPromises(); expect(mockZHController.touchlinkFactoryResetFirst).toHaveBeenCalledTimes(1); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/touchlink/factory_reset', stringify({data: {}, status: 'error', error: 'Failed to factory reset device through Touchlink'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow to touchlink scan', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockZHController.touchlinkScan.mockClear(); mockZHController.touchlinkScan.mockReturnValueOnce([ {ieeeAddr: '0x123', channel: 12}, @@ -3535,7 +3463,7 @@ describe('Extension: Bridge', () => { mockMQTTEvents.message('zigbee2mqtt/bridge/request/touchlink/scan', ''); await flushPromises(); expect(mockZHController.touchlinkScan).toHaveBeenCalledTimes(1); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/touchlink/scan', stringify({ data: { @@ -3547,7 +3475,6 @@ describe('Extension: Bridge', () => { status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -3556,7 +3483,7 @@ describe('Extension: Bridge', () => { const endpoint = device.getEndpoint(1)!; endpoint.bind.mockClear(); endpoint.configureReporting.mockClear(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message( 'zigbee2mqtt/bridge/request/device/configure_reporting', stringify({ @@ -3578,7 +3505,7 @@ describe('Extension: Bridge', () => { [{attribute: 'currentLevel', maximumReportInterval: 10, minimumReportInterval: 1, reportableChange: 1}], undefined, ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/configure_reporting', stringify({ data: { @@ -3593,9 +3520,8 @@ describe('Extension: Bridge', () => { status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}); }); it('Should allow to configure reporting with endpoint as string', async () => { @@ -3603,7 +3529,7 @@ describe('Extension: Bridge', () => { const endpoint = device.getEndpoint(1)!; endpoint.bind.mockClear(); endpoint.configureReporting.mockClear(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message( 'zigbee2mqtt/bridge/request/device/configure_reporting', stringify({ @@ -3625,7 +3551,7 @@ describe('Extension: Bridge', () => { [{attribute: 'currentLevel', maximumReportInterval: 10, minimumReportInterval: 1, reportableChange: 1}], undefined, ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/configure_reporting', stringify({ data: { @@ -3640,16 +3566,15 @@ describe('Extension: Bridge', () => { status: 'ok', }), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}); }); it('Should throw error when configure reporting is called with malformed payload', async () => { const device = devices.bulb; const endpoint = device.getEndpoint(1)!; endpoint.configureReporting.mockClear(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message( 'zigbee2mqtt/bridge/request/device/configure_reporting', stringify({ @@ -3664,11 +3589,10 @@ describe('Extension: Bridge', () => { ); await flushPromises(); expect(endpoint.configureReporting).toHaveBeenCalledTimes(0); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/configure_reporting', stringify({data: {}, status: 'error', error: 'Invalid payload'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -3676,7 +3600,7 @@ describe('Extension: Bridge', () => { const device = devices.bulb; const endpoint = device.getEndpoint(1)!; endpoint.configureReporting.mockClear(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message( 'zigbee2mqtt/bridge/request/device/configure_reporting', stringify({ @@ -3691,11 +3615,10 @@ describe('Extension: Bridge', () => { ); await flushPromises(); expect(endpoint.configureReporting).toHaveBeenCalledTimes(0); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/configure_reporting', stringify({data: {}, status: 'error', error: "Device 'non_existing_device' does not exist"}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -3703,7 +3626,7 @@ describe('Extension: Bridge', () => { const device = devices.bulb; const endpoint = device.getEndpoint(1)!; endpoint.configureReporting.mockClear(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message( 'zigbee2mqtt/bridge/request/device/configure_reporting', stringify({ @@ -3718,11 +3641,10 @@ describe('Extension: Bridge', () => { ); await flushPromises(); expect(endpoint.configureReporting).toHaveBeenCalledTimes(0); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/configure_reporting', stringify({data: {}, status: 'error', error: "Device '0x000b57fffec6a5b2' does not have endpoint 'non_existing_endpoint'"}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -3733,7 +3655,7 @@ describe('Extension: Bridge', () => { fs.writeFileSync(path.join(data.mockDir, 'log', 'log.log'), 'test123'); fs.mkdirSync(path.join(data.mockDir, 'ext_converters', '123')); fs.writeFileSync(path.join(data.mockDir, 'ext_converters', '123', 'myfile.js'), 'test123'); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/backup', ''); await flushPromises(); expect(mockZHController.backup).toHaveBeenCalledTimes(1); @@ -3744,79 +3666,74 @@ describe('Extension: Bridge', () => { expect(mockJSZipFile).toHaveBeenNthCalledWith(4, 'state.json', expect.any(Object)); expect(mockJSZipGenerateAsync).toHaveBeenCalledTimes(1); expect(mockJSZipGenerateAsync).toHaveBeenNthCalledWith(1, {type: 'base64'}); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/backup', stringify({data: {zip: 'THISISBASE64'}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Should allow to restart', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/restart', ''); await flushPromises(); jest.runOnlyPendingTimers(); expect(mockRestart).toHaveBeenCalledTimes(1); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/restart', stringify({data: {}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Change options and apply - homeassistant', async () => { // @ts-expect-error private expect(controller.extensions.find((e) => e.constructor.name === 'HomeAssistant')).toBeUndefined(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/options', stringify({options: {homeassistant: true}})); await flushPromises(); // @ts-expect-error private expect(controller.extensions.find((e) => e.constructor.name === 'HomeAssistant')).not.toBeUndefined(); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), {retain: true, qos: 0}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/options', stringify({data: {restart_required: true}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Change options and apply - log_level', async () => { mockLogger.setLevel('info'); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/options', stringify({options: {advanced: {log_level: 'debug'}}})); await flushPromises(); expect(mockLogger.getLevel()).toStrictEqual('debug'); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), {retain: true, qos: 0}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/options', stringify({data: {restart_required: false}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Change options and apply - log_debug_namespace_ignore', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); const nsIgnore = '^zhc:legacy:fz:(tuya|moes)|^zh:ember:uart:|^zh:controller'; mockMQTTEvents.message('zigbee2mqtt/bridge/request/options', stringify({options: {advanced: {log_debug_namespace_ignore: nsIgnore}}})); await flushPromises(); expect(mockLogger.getDebugNamespaceIgnore()).toStrictEqual(nsIgnore); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), {retain: true, qos: 0}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/options', stringify({data: {restart_required: false}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Change options and apply - log_namespaced_levels', async () => { mockLogger.setLevel('info'); settings.apply({advanced: {log_namespaced_levels: {'zh:zstack': 'warning', 'z2m:mqtt': 'debug'}}}); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message( 'zigbee2mqtt/bridge/request/options', stringify({options: {advanced: {log_namespaced_levels: {'z2m:mqtt': 'warning', 'zh:zstack': null}}}}), @@ -3824,12 +3741,11 @@ describe('Extension: Bridge', () => { await flushPromises(); expect(settings.get().advanced.log_namespaced_levels).toStrictEqual({'z2m:mqtt': 'warning'}); expect(mockLogger.getNamespacedLevels()).toStrictEqual({'z2m:mqtt': 'warning'}); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), {retain: true, qos: 0}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/options', stringify({data: {restart_required: false}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); mockMQTTEvents.message('zigbee2mqtt/bridge/request/options', stringify({options: {advanced: {log_namespaced_levels: {'z2m:mqtt': null}}}})); @@ -3840,70 +3756,65 @@ describe('Extension: Bridge', () => { it('Change options restart required', async () => { settings.apply({serial: {port: '123'}}); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/options', stringify({options: {serial: {port: '/dev/newport'}}})); await flushPromises(); expect(settings.get().serial.port).toBe('/dev/newport'); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/options', stringify({data: {restart_required: true}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Change options array', async () => { expect(settings.get().advanced.ext_pan_id).toStrictEqual([221, 221, 221, 221, 221, 221, 221, 221]); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message( 'zigbee2mqtt/bridge/request/options', stringify({options: {advanced: {ext_pan_id: [220, 221, 221, 221, 221, 221, 221, 221]}}}), ); await flushPromises(); expect(settings.get().advanced.ext_pan_id).toStrictEqual([220, 221, 221, 221, 221, 221, 221, 221]); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/options', stringify({data: {restart_required: true}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Change options with null', async () => { expect(settings.get().serial).toStrictEqual({disable_led: false, port: '/dev/dummy'}); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/options', stringify({options: {serial: {disable_led: false, port: null}}})); await flushPromises(); expect(settings.get().serial).toStrictEqual({disable_led: false}); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/options', stringify({data: {restart_required: true}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Change options invalid payload', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/options', 'I am invalid'); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/options', stringify({data: {}, error: 'Invalid payload', status: 'error'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Change options not valid against schema', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/options', stringify({options: {external_converters: 'true'}})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/options', stringify({data: {}, error: 'external_converters must be array', status: 'error'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -3971,25 +3882,23 @@ describe('Extension: Bridge', () => { }); it('Should publish bridge info, devices and definitions when a device with custom_clusters joined', async () => { - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockZHEvents.deviceJoined({device: devices.bulb_custom_cluster}); await flushPromises(); // console.log(mockMQTT.publish.mock.calls); - expect(mockMQTT.publish).toHaveBeenCalledTimes(5); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(5); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), {retain: true, qos: 0}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/definitions', expect.stringContaining(stringify(CUSTOM_CLUSTERS)), {retain: true, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', stringify({data: {friendly_name: '0x000b57fffec6a5c2', ieee_address: '0x000b57fffec6a5c2'}, type: 'device_joined'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -3997,27 +3906,25 @@ describe('Extension: Bridge', () => { // Adding a device first await mockZHEvents.deviceJoined({device: devices.bulb_custom_cluster}); await flushPromises(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); // After cleaning, reconfigure it mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/configure', devices.bulb_custom_cluster.ieeeAddr); await flushPromises(); // console.log(mockMQTT.publish.mock.calls); - expect(mockMQTT.publish).toHaveBeenCalledTimes(4); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(4); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), {retain: true, qos: 0}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/definitions', expect.stringContaining(stringify(CUSTOM_CLUSTERS)), {retain: true, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/configure', expect.any(String), {retain: false, qos: 0}, - expect.any(Function), ); }); }); diff --git a/test/extensions/configure.test.ts b/test/extensions/configure.test.ts index 4ab7bba995..de24decd48 100644 --- a/test/extensions/configure.test.ts +++ b/test/extensions/configure.test.ts @@ -9,7 +9,7 @@ import stringify from 'json-stable-stringify-without-jsonify'; import {Controller} from '../../lib/controller'; import * as settings from '../../lib/util/settings'; -const mocksClear = [mockMQTT.publish, mockLogger.warning, mockLogger.debug]; +const mocksClear = [mockMQTT.publishAsync, mockLogger.warning, mockLogger.debug]; describe('Extension: Configure', () => { let controller: Controller; @@ -150,22 +150,20 @@ describe('Extension: Configure', () => { await mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/configure', 'remote'); await flushPromises(); expectRemoteConfigured(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/configure', stringify({data: {id: 'remote'}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Fail to configure via MQTT when device does not exist', async () => { await mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/configure', stringify({id: 'not_existing_device'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/configure', stringify({data: {id: 'not_existing_device'}, status: 'error', error: "Device 'not_existing_device' does not exist"}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -175,22 +173,20 @@ describe('Extension: Configure', () => { }); await mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/configure', stringify({id: 'remote'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/configure', stringify({data: {id: 'remote'}, status: 'error', error: 'Failed to configure (Bind timeout after 10s)'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Fail to configure via MQTT when device has no configure', async () => { await mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/configure', stringify({id: '0x0017882104a44559', transaction: 20})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/configure', stringify({data: {id: '0x0017882104a44559'}, status: 'error', error: "Device 'TS0601_thermostat' cannot be configured", transaction: 20}), {retain: false, qos: 0}, - expect.any(Function), ); }); diff --git a/test/extensions/externalConverters.test.ts b/test/extensions/externalConverters.test.ts index e5ba4fb0b9..54308cb90f 100644 --- a/test/extensions/externalConverters.test.ts +++ b/test/extensions/externalConverters.test.ts @@ -51,8 +51,8 @@ const mocksClear = [ devices.bulb.removeFromNetwork, mockZHController.permitJoin, mockZHController.stop, - mockMQTT.end, - mockMQTT.publish, + mockMQTT.endAsync, + mockMQTT.publishAsync, mockLogger.debug, mockLogger.error, ]; diff --git a/test/extensions/externalExtension.test.ts b/test/extensions/externalExtension.test.ts index 20c3bbc6d9..e20aff014d 100644 --- a/test/extensions/externalExtension.test.ts +++ b/test/extensions/externalExtension.test.ts @@ -18,8 +18,8 @@ const mocksClear = [ mockZHController.stop, devices.bulb_color.removeFromNetwork, devices.bulb.removeFromNetwork, - mockMQTT.end, - mockMQTT.publish, + mockMQTT.endAsync, + mockMQTT.publishAsync, mockLogger.debug, mockLogger.error, ]; @@ -63,12 +63,11 @@ describe('Extension: ExternalExtension', () => { controller = new Controller(jest.fn(), jest.fn()); await controller.start(); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'test', {retain: false, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'test', {retain: false, qos: 0}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/extensions', stringify([{name: 'exampleExtension.js', code: extensionCode}]), {retain: true, qos: 0}, - expect.any(Function), ); }); @@ -78,20 +77,18 @@ describe('Extension: ExternalExtension', () => { controller = new Controller(jest.fn(), jest.fn()); await controller.start(); await flushPromises(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/extension/save', stringify({name: 'foo.js', code: extensionCode})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/extensions', stringify([{name: 'foo.js', code: extensionCode}]), {retain: true, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/example/extension', 'call from constructor', {retain: false, qos: 0}, - expect.any(Function), ); expect(mkdirSyncSpy).toHaveBeenCalledWith(extensionPath); }); @@ -101,17 +98,16 @@ describe('Extension: ExternalExtension', () => { controller = new Controller(jest.fn(), jest.fn()); await controller.start(); await flushPromises(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/extension/save', stringify({name: 'foo.js', code: extensionCode})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/extension/save', expect.any(String), {retain: false, qos: 0}, - expect.any(Function), ); - const payload = JSON.parse(mockMQTT.publish.mock.calls[0][1]); + const payload = JSON.parse(mockMQTT.publishAsync.mock.calls[0][1]); expect(payload).toEqual(expect.objectContaining({data: {}, status: 'error'})); expect(payload.error).toMatch('Unexpected identifier'); }); @@ -125,25 +121,23 @@ describe('Extension: ExternalExtension', () => { controller = new Controller(jest.fn(), jest.fn()); await controller.start(); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'test', {retain: false, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'test', {retain: false, qos: 0}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/extensions', stringify([{name: 'exampleExtension.js', code: extensionCode}]), {retain: true, qos: 0}, - expect.any(Function), ); mockMQTTEvents.message('zigbee2mqtt/bridge/request/extension/remove', stringify({name: 'exampleExtension.js'})); await flushPromises(); expect(unlinkSyncSpy).toHaveBeenCalledWith(extensionFilePath); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/extension/remove', stringify({name: 'non existing.js'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/extension/remove', stringify({data: {}, status: 'error', error: "Extension non existing.js doesn't exists"}), {retain: false, qos: 0}, - expect.any(Function), ); }); }); diff --git a/test/extensions/frontend.test.ts b/test/extensions/frontend.test.ts index dbb9417c00..62aca52fdf 100644 --- a/test/extensions/frontend.test.ts +++ b/test/extensions/frontend.test.ts @@ -118,7 +118,7 @@ const mocksClear = [ mockWSClient.terminate, mockNodeStatic, mockFinalHandler, - mockMQTT.publish, + mockMQTT.publishAsync, mockLogger.error, ]; @@ -230,12 +230,12 @@ describe('Extension: Frontend', () => { expect(mockWSClient.send).toHaveBeenCalledWith(stringify({topic: 'remote', payload: {brightness: 255}})); // Message - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockWSClient.send.mockClear(); mockWSClientEvents.message(stringify({topic: 'bulb_color/set', payload: {state: 'ON'}}), false); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(1); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', stringify({ state: 'ON', @@ -244,7 +244,6 @@ describe('Extension: Frontend', () => { update: {state: null, installed_version: -1, latest_version: -1}, }), {retain: false, qos: 0}, - expect.any(Function), ); mockWSClientEvents.message(undefined, false); mockWSClientEvents.message('', false); diff --git a/test/extensions/groups.test.ts b/test/extensions/groups.test.ts index 2dffc71b6f..0dd538c879 100644 --- a/test/extensions/groups.test.ts +++ b/test/extensions/groups.test.ts @@ -39,7 +39,7 @@ describe('Extension: Groups', () => { resetGroupMembers(); data.writeDefaultConfiguration(); settings.reRead(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); groups.gledopto_group.command.mockClear(); zhcToZigbee.__clearStore__(); // @ts-expect-error private @@ -52,26 +52,25 @@ describe('Extension: Groups', () => { const group = groups.group_1; group.members.push(endpoint); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); const payload = {data: {onOff: 1}, cluster: 'genOnOff', device, endpoint, type: 'attributeReport', linkquality: 10}; await mockZHEvents.message(payload); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(2); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0}); }); it('Should not republish identical optimistic group states', async () => { const device1 = devices.bulb_2; const device2 = devices.bulb_color_2; - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockZHEvents.message({ data: {onOff: 1}, cluster: 'genOnOff', @@ -89,37 +88,32 @@ describe('Extension: Groups', () => { linkquality: 10, }); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(6); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(6); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/group_tradfri_remote', stringify({state: 'ON'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb_2', stringify({state: 'ON'}), {retain: false, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_2', stringify({state: 'ON'}), {retain: false, qos: 0}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color_2', stringify({state: 'ON'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/group_with_tradfri', stringify({state: 'ON'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/ha_discovery_group', stringify({state: 'ON'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/switch_group', stringify({state: 'ON'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -129,17 +123,16 @@ describe('Extension: Groups', () => { const group = groups.group_1; group.members.push(endpoint); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(2); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0}); }); it('Should not publish state change when group changes state and device is disabled', async () => { @@ -149,11 +142,11 @@ describe('Extension: Groups', () => { group.members.push(endpoint); settings.set(['devices', device.ieeeAddr, 'disabled'], true); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(1); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0}); }); it('Should publish state change for group when members state change', async () => { @@ -163,66 +156,60 @@ describe('Extension: Groups', () => { const group = groups.group_1; group.members.push(endpoint); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(2); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0}); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'OFF'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(2); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/group_1', stringify({state: 'OFF'}), {retain: false, qos: 0}, - expect.any(Function), ); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(2); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0}); }); it('Should publish state of device with endpoint name', async () => { const group = groups.gledopto_group; - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/gledopto_group/set', stringify({state: 'ON'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(2); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/GLEDOPTO_2ID', stringify({state_cct: 'ON'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/gledopto_group', stringify({state: 'ON'}), {retain: false, qos: 0}, - expect.any(Function), ); expect(group.command).toHaveBeenCalledTimes(1); expect(group.command).toHaveBeenCalledWith('genOnOff', 'on', {}, {}); @@ -231,21 +218,19 @@ describe('Extension: Groups', () => { it('Should publish state of group when specific state of specific endpoint is changed', async () => { const group = groups.gledopto_group; - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/GLEDOPTO_2ID/set', stringify({state_cct: 'ON'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(2); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/GLEDOPTO_2ID', stringify({state_cct: 'ON'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/gledopto_group', stringify({state: 'ON'}), {retain: false, qos: 0}, - expect.any(Function), ); expect(group.command).toHaveBeenCalledTimes(0); }); @@ -257,17 +242,16 @@ describe('Extension: Groups', () => { group.members.push(endpoint); settings.set(['groups'], {1: {friendly_name: 'group_1', retain: false, filtered_attributes: ['brightness']}}); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON', brightness: 100})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(2); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', stringify({state: 'ON', brightness: 100}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0}); }); it('Shouldnt publish group state change when a group is not optimistic', async () => { @@ -277,17 +261,16 @@ describe('Extension: Groups', () => { group.members.push(endpoint); settings.set(['groups'], {1: {friendly_name: 'group_1', optimistic: false, retain: false}}); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); const payload = {data: {onOff: 1}, cluster: 'genOnOff', device, endpoint, type: 'attributeReport', linkquality: 10}; await mockZHEvents.message(payload); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(1); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -297,18 +280,17 @@ describe('Extension: Groups', () => { groups.group_1.members.push(endpoint); groups.group_2.members.push(endpoint); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(3); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/group_2', stringify({state: 'ON'}), {retain: false, qos: 0}, expect.any(Function)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_2', stringify({state: 'ON'}), {retain: false, qos: 0}); }); it('Should not publish state change off if any lights within are still on when changed via device', async () => { @@ -322,16 +304,15 @@ describe('Extension: Groups', () => { await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'})); await flushPromises(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(1); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -349,24 +330,22 @@ describe('Extension: Groups', () => { await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'})); await flushPromises(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(2); - expect(mockMQTT.publish).toHaveBeenNthCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith( 1, 'zigbee2mqtt/group_1', stringify({state: 'OFF'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenNthCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith( 2, 'zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -381,22 +360,20 @@ describe('Extension: Groups', () => { await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'})); await flushPromises(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/group_2/set', stringify({state: 'OFF'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(2); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/group_2', stringify({state: 'OFF'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -414,24 +391,22 @@ describe('Extension: Groups', () => { await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'})); await flushPromises(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'})); await mockMQTTEvents.message('zigbee2mqtt/bulb/set', stringify({state: 'OFF'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(3); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({state: 'OFF'}), {retain: true, qos: 0}, expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({state: 'OFF'}), {retain: true, qos: 0}); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/group_1', stringify({state: 'OFF'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -446,33 +421,30 @@ describe('Extension: Groups', () => { settings.set(['groups'], { 1: {friendly_name: 'group_1', retain: false}, }); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF', color_temp: 200})); await mockMQTTEvents.message('zigbee2mqtt/bulb/set', stringify({state: 'ON', color_temp: 250})); await flushPromises(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({color_temp: 300})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(3); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', stringify({color_mode: 'color_temp', color_temp: 300, state: 'OFF'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', stringify({color_mode: 'color_temp', color_temp: 300, state: 'ON'}), {retain: true, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/group_1', stringify({color_mode: 'color_temp', color_temp: 300, state: 'ON'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -490,25 +462,23 @@ describe('Extension: Groups', () => { await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'})); await flushPromises(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); // @ts-expect-error private controller.state.state = {}; await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(2); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/group_1', stringify({state: 'OFF'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -517,19 +487,18 @@ describe('Extension: Groups', () => { const endpoint = device.getEndpoint(1); const group = groups.group_1; expect(group.members.length).toBe(0); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message( 'zigbee2mqtt/bridge/request/group/members/add', stringify({transaction: '123', group: 'group_1', device: 'bulb_color'}), ); await flushPromises(); expect(group.members).toStrictEqual([endpoint]); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/add', stringify({data: {device: 'bulb_color', endpoint: 'default', group: 'group_1'}, transaction: '123', status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -542,16 +511,15 @@ describe('Extension: Groups', () => { throw new Error('timeout'); }); await flushPromises(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: 'bulb_color'})); await flushPromises(); expect(group.members).toStrictEqual([]); - expect(mockMQTT.publish).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/add', stringify({data: {}, status: 'error', error: 'Failed to add from group (timeout)'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -561,16 +529,15 @@ describe('Extension: Groups', () => { const group = groups['group/with/slashes']; settings.set(['groups'], {99: {friendly_name: 'group/with/slashes', retain: false}}); expect(group.members.length).toBe(0); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group/with/slashes', device: 'bulb_color'})); await flushPromises(); expect(group.members).toStrictEqual([endpoint]); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/add', stringify({data: {device: 'bulb_color', endpoint: 'default', group: 'group/with/slashes'}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -579,19 +546,18 @@ describe('Extension: Groups', () => { const endpoint = device.getEndpoint(3)!; const group = groups.group_1; expect(group.members.length).toBe(0); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message( 'zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: 'wall_switch_double', endpoint: 'right'}), ); await flushPromises(); expect(group.members).toStrictEqual([endpoint]); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/add', stringify({data: {device: 'wall_switch_double', endpoint: 'right', group: 'group_1'}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -600,7 +566,7 @@ describe('Extension: Groups', () => { const endpoint = device.getEndpoint(3)!; const group = groups.group_1; expect(group.members.length).toBe(0); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message( 'zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: 'wall_switch_double', endpoint: 'right'}), @@ -612,12 +578,11 @@ describe('Extension: Groups', () => { ); await flushPromises(); expect(group.members).toStrictEqual([endpoint]); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/add', stringify({data: {device: 'wall_switch_double', endpoint: 'right', group: 'group_1'}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -626,16 +591,15 @@ describe('Extension: Groups', () => { const endpoint = device.getEndpoint(1)!; const group = groups.group_1; group.members.push(endpoint); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1', device: 'bulb_color'})); await flushPromises(); expect(group.members).toStrictEqual([]); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/remove', stringify({data: {device: 'bulb_color', endpoint: 'default', group: 'group_1'}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -644,19 +608,18 @@ describe('Extension: Groups', () => { const endpoint = device.getEndpoint(1)!; const group = groups.group_1; group.members.push(endpoint); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message( 'zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1', device: 'bulb_color', skip_disable_reporting: true}), ); await flushPromises(); expect(group.members).toStrictEqual([]); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/remove', stringify({data: {device: 'bulb_color', endpoint: 'default', group: 'group_1'}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -665,19 +628,18 @@ describe('Extension: Groups', () => { const endpoint = device.getEndpoint(3)!; const group = groups.group_1; group.members.push(endpoint); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message( 'zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1', device: '0x0017880104e45542', endpoint: '3'}), ); await flushPromises(); expect(group.members).toStrictEqual([]); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/remove', stringify({data: {device: '0x0017880104e45542', endpoint: '3', group: 'group_1'}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -686,19 +648,18 @@ describe('Extension: Groups', () => { const endpoint = device.getEndpoint(3)!; const group = groups.group_1; group.members.push(endpoint); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message( 'zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1', device: 'wall_switch_double', endpoint: '3'}), ); await flushPromises(); expect(group.members).toStrictEqual([]); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/remove', stringify({data: {device: 'wall_switch_double', endpoint: '3', group: 'group_1'}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -707,108 +668,101 @@ describe('Extension: Groups', () => { const endpoint = device.getEndpoint(3)!; const group = groups.group_1; group.members.push(endpoint); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message( 'zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1', device: '0x0017880104e45542', endpoint: 'right'}), ); await flushPromises(); expect(group.members).toStrictEqual([]); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/remove', stringify({data: {device: '0x0017880104e45542', endpoint: 'right', group: 'group_1'}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Remove from group all', async () => { const group = groups.group_1; groups.group_1.members.push(devices.QBKG03LM.endpoints[2]); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/members/remove_all', stringify({device: '0x0017880104e45542', endpoint: 'right'})); await flushPromises(); expect(group.members).toStrictEqual([]); - expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/remove_all', stringify({data: {device: '0x0017880104e45542', endpoint: 'right'}, status: 'ok'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Error when adding to non-existing group', async () => { mockLogger.error.mockClear(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1_not_existing', device: 'bulb_color'})); await flushPromises(); - expect(mockMQTT.publish).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/remove', stringify({data: {}, status: 'error', error: "Group 'group_1_not_existing' does not exist"}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Error when adding a non-existing device', async () => { mockLogger.error.mockClear(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: 'bulb_color_not_existing'})); await flushPromises(); - expect(mockMQTT.publish).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/add', stringify({data: {}, status: 'error', error: "Device 'bulb_color_not_existing' does not exist"}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Error when adding a non-existing endpoint', async () => { mockLogger.error.mockClear(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message( 'zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: 'bulb_color', endpoint: 'not_existing_endpoint'}), ); await flushPromises(); - expect(mockMQTT.publish).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/add', stringify({data: {}, status: 'error', error: "Device 'bulb_color' does not have endpoint 'not_existing_endpoint'"}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Error when invalid payload', async () => { mockLogger.error.mockClear(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', devicez: 'bulb_color'})); await flushPromises(); - expect(mockMQTT.publish).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/add', stringify({data: {}, status: 'error', error: 'Invalid payload'}), {retain: false, qos: 0}, - expect.any(Function), ); }); it('Error when add/remove with invalid payload', async () => { mockLogger.error.mockClear(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/members/add', stringify({groupz: 'group_1', device: 'bulb_color'})); await flushPromises(); - expect(mockMQTT.publish).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object)); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/add', stringify({data: {}, status: 'error', error: 'Invalid payload'}), {retain: false, qos: 0}, - expect.any(Function), ); }); @@ -819,50 +773,44 @@ describe('Extension: Groups', () => { group.members.push(bulbColor.getEndpoint(1)!); group.members.push(bulbColorTemp.getEndpoint(1)!); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({color_temp: 50})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(3); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', stringify({color_mode: 'color_temp', color_temp: 50}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/group_1', stringify({color_mode: 'color_temp', color_temp: 50}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', stringify({color_mode: 'color_temp', color_temp: 50}), {retain: true, qos: 0}, - expect.any(Function), ); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({color: {x: 0.5, y: 0.3}})); await flushPromises(); - expect(mockMQTT.publish).toHaveBeenCalledTimes(3); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3); + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', stringify({color: {x: 0.5, y: 0.3}, color_mode: 'xy', color_temp: 548}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/group_1', stringify({color: {x: 0.5, y: 0.3}, color_mode: 'xy', color_temp: 548}), {retain: false, qos: 0}, - expect.any(Function), ); - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', stringify({color_mode: 'color_temp', color_temp: 548}), {retain: true, qos: 0}, - expect.any(Function), ); }); }); diff --git a/test/extensions/homeassistant.test.ts b/test/extensions/homeassistant.test.ts index ab28e89679..ee1d603d64 100644 --- a/test/extensions/homeassistant.test.ts +++ b/test/extensions/homeassistant.test.ts @@ -13,7 +13,7 @@ import {Controller} from '../../lib/controller'; import HomeAssistant from '../../lib/extension/homeassistant'; import * as settings from '../../lib/util/settings'; -const mocksClear = [mockMQTT.publish, mockLogger.debug, mockLogger.warning, mockLogger.error]; +const mocksClear = [mockMQTT.publishAsync, mockLogger.debug, mockLogger.warning, mockLogger.error]; describe('Extension: HomeAssistant', () => { let controller: Controller; @@ -55,7 +55,7 @@ describe('Extension: HomeAssistant', () => { data.writeDefaultConfiguration(); settings.reRead(); data.writeEmptyState(); - mockMQTT.publish.mockClear(); + mockMQTT.publishAsync.mockClear(); mockSleep.mock(); controller = new Controller(jest.fn(), jest.fn()); await controller.start(); @@ -152,11 +152,10 @@ describe('Extension: HomeAssistant', () => { origin: origin, }; - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'homeassistant/light/1221051039810110150109113116116_9/light/config', stringify(payload), {retain: true, qos: 1}, - expect.any(Function), ); payload = { @@ -180,11 +179,10 @@ describe('Extension: HomeAssistant', () => { value_template: '{{ value_json.state }}', }; - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'homeassistant/switch/1221051039810110150109113116116_9/switch/config', stringify(payload), {retain: true, qos: 1}, - expect.any(Function), ); payload = { @@ -207,11 +205,10 @@ describe('Extension: HomeAssistant', () => { enabled_by_default: true, }; - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/temperature/config', stringify(payload), {retain: true, qos: 1}, - expect.any(Function), ); payload = { @@ -234,11 +231,10 @@ describe('Extension: HomeAssistant', () => { availability: [{topic: 'zigbee2mqtt/bridge/state', value_template: '{{ value_json.state }}'}], }; - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/humidity/config', stringify(payload), {retain: true, qos: 1}, - expect.any(Function), ); payload = { @@ -261,11 +257,10 @@ describe('Extension: HomeAssistant', () => { availability: [{topic: 'zigbee2mqtt/bridge/state', value_template: '{{ value_json.state }}'}], }; - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/pressure/config', stringify(payload), {retain: true, qos: 1}, - expect.any(Function), ); payload = { @@ -289,11 +284,10 @@ describe('Extension: HomeAssistant', () => { availability: [{topic: 'zigbee2mqtt/bridge/state', value_template: '{{ value_json.state }}'}], }; - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/battery/config', stringify(payload), {retain: true, qos: 1}, - expect.any(Function), ); payload = { @@ -318,11 +312,10 @@ describe('Extension: HomeAssistant', () => { availability: [{topic: 'zigbee2mqtt/bridge/state', value_template: '{{ value_json.state }}'}], }; - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/linkquality/config', stringify(payload), {retain: true, qos: 1}, - expect.any(Function), ); payload = { @@ -345,11 +338,10 @@ describe('Extension: HomeAssistant', () => { value_template: '{{ value_json.state_left }}', }; - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'homeassistant/switch/0x0017880104e45542/switch_left/config', stringify(payload), {retain: true, qos: 1}, - expect.any(Function), ); payload = { @@ -372,11 +364,10 @@ describe('Extension: HomeAssistant', () => { value_template: '{{ value_json.state_right }}', }; - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'homeassistant/switch/0x0017880104e45542/switch_right/config', stringify(payload), {retain: true, qos: 1}, - expect.any(Function), ); payload = { @@ -404,11 +395,10 @@ describe('Extension: HomeAssistant', () => { origin: origin, }; - expect(mockMQTT.publish).toHaveBeenCalledWith( + expect(mockMQTT.publishAsync).toHaveBeenCalledWith( 'homeassistant/light/0x000b57fffec6a5b2/light/config', stringify(payload), {retain: true, qos: 1}, - expect.any(Function), ); payload = { @@ -432,11 +422,10 @@ describe('Extension: HomeAssistant', () => { '{%- set buttons = value_json.action|regex_findall_index(^(?P