diff --git a/lib/controller.js b/lib/controller.js index c7a437b2..36155ed0 100644 --- a/lib/controller.js +++ b/lib/controller.js @@ -6,6 +6,7 @@ const logger = require('./util/logger'); const settings = require('./util/settings'); const objectAssignDeep = require('object-assign-deep'); const utils = require('./util/utils'); +const stringify = require('json-stable-stringify'); const fs = require('fs'); const data = require('./util/data'); const path = require('path'); @@ -218,7 +219,7 @@ class Controller { if (type === 'message') { logger.debug( `Received Zigbee message from '${name}', type '${data.type}', cluster '${data.cluster}'` + - `, data '${JSON.stringify(data.data)}' from endpoint ${data.endpoint.ID}` + + `, data '${stringify(data.data)}' from endpoint ${data.endpoint.ID}` + (data.hasOwnProperty('groupID') ? ` with groupID ${data.groupID}` : ``), ); } else if (type === 'deviceJoined') { @@ -325,16 +326,13 @@ class Controller { } if (Object.entries(messagePayload).length) { - if (settings.get().experimental.output === 'attribute_and_json') { - await this.mqtt.publish(resolvedEntity.name, JSON.stringify(messagePayload), options); + const output = settings.get().experimental.output; + if (output === 'attribute_and_json' || output === 'json') { + await this.mqtt.publish(resolvedEntity.name, stringify(messagePayload), options); + } + + if (output === 'attribute_and_json' || output === 'attribute') { await this.iteratePayloadAttributeOutput(`${resolvedEntity.name}/`, messagePayload, options); - } else if (settings.get().experimental.output === 'json') { - await this.mqtt.publish(resolvedEntity.name, JSON.stringify(messagePayload), options); - } else { - /* istanbul ignore else */ - if (settings.get().experimental.output === 'attribute') { - await this.iteratePayloadAttributeOutput(`${resolvedEntity.name}/`, messagePayload, options); - } } } @@ -359,7 +357,7 @@ class Controller { } else if (typeof subPayload === 'object') { this.iteratePayloadAttributeOutput(`${topicRoot}${key}-`, subPayload, options); } else { - message = typeof subPayload === 'string' ? subPayload : JSON.stringify(subPayload); + message = typeof subPayload === 'string' ? subPayload : stringify(subPayload); } if (message !== null) { diff --git a/lib/extension/bind.js b/lib/extension/bind.js index c81ce86b..04bf8e95 100644 --- a/lib/extension/bind.js +++ b/lib/extension/bind.js @@ -4,6 +4,7 @@ const utils = require('../util/utils'); const legacyTopicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/(bind|unbind)/.+$`); const topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/device/(bind|unbind)`); const Extension = require('./extension'); +const stringify = require('json-stable-stringify'); const clusterCandidates = ['genScenes', 'genOnOff', 'genLevelCtrl', 'lightingColorCtrl', 'closuresWindowCovering']; @@ -106,7 +107,7 @@ class Bind extends Extension { if (settings.get().advanced.legacy_api) { this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `device_${type}`, + stringify({type: `device_${type}`, message: {from: sourceName, to: targetName, cluster}}), ); } @@ -121,7 +122,7 @@ class Bind extends Extension { if (settings.get().advanced.legacy_api) { this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `device_${type}_failed`, + stringify({type: `device_${type}_failed`, message: {from: sourceName, to: targetName, cluster}}), ); } @@ -137,7 +138,7 @@ class Bind extends Extension { if (settings.get().advanced.legacy_api) { this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `device_${type}_failed`, message: {from: sourceName, to: targetName}}), + stringify({type: `device_${type}_failed`, message: {from: sourceName, to: targetName}}), ); } } else if (failedClusters.length === attemptedClusters.length) { @@ -151,7 +152,7 @@ class Bind extends Extension { const triggeredViaLegacyApi = topic.match(legacyTopicRegex); if (!triggeredViaLegacyApi) { const response = utils.getResponse(message, responseData, error); - await this.mqtt.publish(`bridge/response/device/${type}`, JSON.stringify(response)); + await this.mqtt.publish(`bridge/response/device/${type}`, stringify(response)); } if (error) { diff --git a/lib/extension/bridge.js b/lib/extension/bridge.js index 935c9364..877945fb 100644 --- a/lib/extension/bridge.js +++ b/lib/extension/bridge.js @@ -4,6 +4,7 @@ const Extension = require('./extension'); const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters'); const settings = require('../util/settings'); const Transport = require('winston-transport'); +const stringify = require('json-stable-stringify'); const requestRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/request/(.*)`); @@ -47,7 +48,7 @@ class Bridge extends Extension { const mqtt = this.mqtt; class EventTransport extends Transport { log(info, callback) { - const payload = JSON.stringify({message: info.message, level: info.level}); + const payload = stringify({message: info.message, level: info.level}); mqtt.publish(`bridge/logging`, payload, {}, settings.get().mqtt.base_topic, true); callback(); } @@ -63,11 +64,11 @@ class Bridge extends Extension { try { const response = await this.requestLookup[match[1].toLowerCase()](message); - await this.mqtt.publish(`bridge/response/${match[1]}`, JSON.stringify(response)); + await this.mqtt.publish(`bridge/response/${match[1]}`, stringify(response)); } catch (error) { logger.error(`Request '${topic}' failed with error: '${error.message}'`); const response = utils.getResponse(message, {}, error.message); - await this.mqtt.publish(`bridge/response/${match[1]}`, JSON.stringify(response)); + await this.mqtt.publish(`bridge/response/${match[1]}`, stringify(response)); } } } @@ -99,7 +100,7 @@ class Bridge extends Extension { await this.mqtt.publish( 'bridge/event', - JSON.stringify({type: utils.toSnakeCase(type), data: payload}), + stringify({type: utils.toSnakeCase(type), data: payload}), {retain: false, qos: 0}, ); } @@ -366,7 +367,7 @@ class Bridge extends Extension { permit_join: await this.zigbee.getPermitJoin(), }; - await this.mqtt.publish('bridge/info', JSON.stringify(payload), {retain: true, qos: 0}); + await this.mqtt.publish('bridge/info', stringify(payload), {retain: true, qos: 0}); } async publishDevices() { @@ -395,7 +396,7 @@ class Bridge extends Extension { }; }); - await this.mqtt.publish('bridge/devices', JSON.stringify(devices), {retain: true, qos: 0}); + await this.mqtt.publish('bridge/devices', stringify(devices), {retain: true, qos: 0}); } async publishGroups() { @@ -413,7 +414,7 @@ class Bridge extends Extension { }; }); - await this.mqtt.publish('bridge/groups', JSON.stringify(groups), {retain: true, qos: 0}); + await this.mqtt.publish('bridge/groups', stringify(groups), {retain: true, qos: 0}); } } diff --git a/lib/extension/configure.js b/lib/extension/configure.js index 62cfcf4d..1604c451 100644 --- a/lib/extension/configure.js +++ b/lib/extension/configure.js @@ -2,6 +2,7 @@ const settings = require('../util/settings'); const utils = require('../util/utils'); const logger = require('../util/logger'); const Extension = require('./extension'); +const stringify = require('json-stable-stringify'); /** * This extension calls the zigbee-herdsman-converters definition configure() method @@ -97,7 +98,7 @@ class Configure extends Extension { } const response = utils.getResponse(message, {id: ID}, error); - await this.mqtt.publish(`bridge/response/device/configure`, JSON.stringify(response)); + await this.mqtt.publish(`bridge/response/device/configure`, stringify(response)); } } diff --git a/lib/extension/groups.js b/lib/extension/groups.js index b0825146..1eb07909 100644 --- a/lib/extension/groups.js +++ b/lib/extension/groups.js @@ -3,6 +3,7 @@ const logger = require('../util/logger'); const Extension = require('./extension'); const utils = require('../util/utils'); const postfixes = utils.getEndpointNames(); +const stringify = require('json-stable-stringify'); const topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/group/members/(remove|add|remove_all)$`); @@ -188,7 +189,7 @@ class Groups extends Extension { }; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `device_group_${type}_failed`, message: payload}), + stringify({type: `device_group_${type}_failed`, message: payload}), ); } @@ -209,7 +210,7 @@ class Groups extends Extension { }; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `device_group_${type}_failed`, message: payload}), + stringify({type: `device_group_${type}_failed`, message: payload}), ); } @@ -289,7 +290,7 @@ class Groups extends Extension { const payload = {friendly_name: resolvedEntityDevice.name, group: resolvedEntityGroup.name}; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `device_group_add`, message: payload}), + stringify({type: `device_group_add`, message: payload}), ); } } else if (type === 'remove') { @@ -302,7 +303,7 @@ class Groups extends Extension { const payload = {friendly_name: resolvedEntityDevice.name, group: resolvedEntityGroup.name}; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `device_group_remove`, message: payload}), + stringify({type: `device_group_remove`, message: payload}), ); } } else { // remove_all @@ -316,7 +317,7 @@ class Groups extends Extension { const payload = {friendly_name: resolvedEntityDevice.name}; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `device_group_remove_all`, message: payload}), + stringify({type: `device_group_remove_all`, message: payload}), ); } } @@ -328,7 +329,7 @@ class Groups extends Extension { if (!triggeredViaLegacyApi) { const response = utils.getResponse(message, responseData, error); - await this.mqtt.publish(`bridge/response/group/members/${type}`, JSON.stringify(response)); + await this.mqtt.publish(`bridge/response/group/members/${type}`, stringify(response)); } if (error) { diff --git a/lib/extension/homeassistant.js b/lib/extension/homeassistant.js index cade2703..4c8cc16c 100644 --- a/lib/extension/homeassistant.js +++ b/lib/extension/homeassistant.js @@ -5,6 +5,7 @@ const utils = require('../util/utils'); const zigbee2mqttVersion = require('../../package.json').version; const Extension = require('./extension'); const objectAssignDeep = require(`object-assign-deep`); +const stringify = require('json-stable-stringify'); const cfg = { // Binary sensor @@ -1947,7 +1948,7 @@ class HomeAssistant extends Extension { } await this.mqtt.publish( - `${data.entity.name}/${endpoint}`, JSON.stringify(payload), {}, + `${data.entity.name}/${endpoint}`, stringify(payload), {}, ); } } @@ -1993,7 +1994,7 @@ class HomeAssistant extends Extension { device: this.getDevicePayload(data.entity), }; - await this.mqtt.publish(topic, JSON.stringify(payload), {retain: true, qos: 0}, this.discoveryTopic); + await this.mqtt.publish(topic, stringify(payload), {retain: true, qos: 0}, this.discoveryTopic); this.discoveredTriggers[device.ieeeAddr].add(discoveredKey); } @@ -2237,7 +2238,7 @@ class HomeAssistant extends Extension { } } - this.mqtt.publish(topic, JSON.stringify(payload), {retain: true, qos: 0}, this.discoveryTopic); + this.mqtt.publish(topic, stringify(payload), {retain: true, qos: 0}, this.discoveryTopic); }); this.discovered[device.ieeeAddr] = true; diff --git a/lib/extension/legacy/bridgeLegacy.js b/lib/extension/legacy/bridgeLegacy.js index a1add617..8ccb3b3b 100644 --- a/lib/extension/legacy/bridgeLegacy.js +++ b/lib/extension/legacy/bridgeLegacy.js @@ -4,6 +4,7 @@ const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters'); const utils = require('../../util/utils'); const assert = require('assert'); const Extension = require('../extension'); +const stringify = require('json-stable-stringify'); const configRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/config/((?:\\w+/get)|(?:\\w+/factory_reset)|(?:\\w+))`); @@ -66,7 +67,7 @@ class BridgeLegacy extends Extension { logger.info(`Whitelisted '${entity.friendlyName}'`); this.mqtt.publish( 'bridge/log', - JSON.stringify({type: 'device_whitelisted', message: {friendly_name: entity.friendlyName}}), + stringify({type: 'device_whitelisted', message: {friendly_name: entity.friendlyName}}), ); } catch (error) { logger.error(`Failed to whitelist '${message}' '${error}'`); @@ -90,7 +91,7 @@ class BridgeLegacy extends Extension { const entity = settings.getEntity(json.friendly_name); assert(entity, `Entity '${json.friendly_name}' does not exist`); settings.changeEntityOptions(entity.ID, json.options); - logger.info(`Changed device specific options of '${json.friendly_name}' (${JSON.stringify(json.options)})`); + logger.info(`Changed device specific options of '${json.friendly_name}' (${stringify(json.options)})`); } async permitJoin(topic, message) { @@ -176,9 +177,9 @@ class BridgeLegacy extends Extension { }); if (topic.split('/').pop() == 'get') { - this.mqtt.publish(`bridge/config/devices`, JSON.stringify(devices), {}); + this.mqtt.publish(`bridge/config/devices`, stringify(devices), {}); } else { - this.mqtt.publish('bridge/log', JSON.stringify({type: 'devices', message: devices})); + this.mqtt.publish('bridge/log', stringify({type: 'devices', message: devices})); } } @@ -189,7 +190,7 @@ class BridgeLegacy extends Extension { return group; }); - this.mqtt.publish('bridge/log', JSON.stringify({type: 'groups', message: payload})); + this.mqtt.publish('bridge/log', stringify({type: 'groups', message: payload})); } rename(topic, message) { @@ -233,7 +234,7 @@ class BridgeLegacy extends Extension { this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `${isGroup ? 'group' : 'device'}_renamed`, message: {from, to}}), + stringify({type: `${isGroup ? 'group' : 'device'}_renamed`, message: {from, to}}), ); } catch (error) { logger.error(`Failed to rename - ${from} to ${to}`); @@ -265,7 +266,7 @@ class BridgeLegacy extends Extension { const group = settings.addGroup(name, id); this.zigbee.createGroup(group.ID); - this.mqtt.publish('bridge/log', JSON.stringify({type: `group_added`, message: name})); + this.mqtt.publish('bridge/log', stringify({type: `group_added`, message: name})); logger.info(`Added group '${name}'`); } @@ -281,7 +282,7 @@ class BridgeLegacy extends Extension { } settings.removeGroup(message); - this.mqtt.publish('bridge/log', JSON.stringify({type: `group_removed`, message})); + this.mqtt.publish('bridge/log', stringify({type: `group_removed`, message})); logger.info(`Removed group '${name}'`); } @@ -308,7 +309,7 @@ class BridgeLegacy extends Extension { if (!entity) { logger.error(`Cannot ${lookup[action][2]}, device '${message}' does not exist`); - this.mqtt.publish('bridge/log', JSON.stringify({type: `device_${lookup[action][0]}_failed`, message})); + this.mqtt.publish('bridge/log', stringify({type: `device_${lookup[action][0]}_failed`, message})); return; } @@ -323,7 +324,7 @@ class BridgeLegacy extends Extension { this.state.remove(entity.settings.ID); logger.info(`Successfully ${lookup[action][0]} ${entity.settings.friendlyName}`); - this.mqtt.publish('bridge/log', JSON.stringify({type: `device_${lookup[action][0]}`, message})); + this.mqtt.publish('bridge/log', stringify({type: `device_${lookup[action][0]}`, message})); }; try { @@ -340,7 +341,7 @@ class BridgeLegacy extends Extension { // eslint-disable-next-line logger.error(`See https://www.zigbee2mqtt.io/information/mqtt_topics_and_message_structure.html#zigbee2mqttbridgeconfigremove for more info`); - this.mqtt.publish('bridge/log', JSON.stringify({type: `device_${lookup[action][0]}_failed`, message})); + this.mqtt.publish('bridge/log', stringify({type: `device_${lookup[action][0]}_failed`, message})); } if (action === 'ban') { @@ -383,7 +384,7 @@ class BridgeLegacy extends Extension { permit_join: await this.zigbee.getPermitJoin(), }; - await this.mqtt.publish(topic, JSON.stringify(payload), {retain: true, qos: 0}); + await this.mqtt.publish(topic, stringify(payload), {retain: true, qos: 0}); } onZigbeeEvent(type, data, resolvedEntity) { @@ -394,7 +395,7 @@ class BridgeLegacy extends Extension { if (type === 'deviceJoined') { this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `device_connected`, message: {friendly_name: resolvedEntity.name}}), + stringify({type: `device_connected`, message: {friendly_name: resolvedEntity.name}}), ); } else if (type === 'deviceInterview') { if (data.status === 'successful') { @@ -403,20 +404,20 @@ class BridgeLegacy extends Extension { const log = {friendly_name: resolvedEntity.name, model, vendor, description, supported: true}; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `pairing`, message: 'interview_successful', meta: log}), + stringify({type: `pairing`, message: 'interview_successful', meta: log}), ); } else { const meta = {friendly_name: resolvedEntity.name, supported: false}; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `pairing`, message: 'interview_successful', meta}), + stringify({type: `pairing`, message: 'interview_successful', meta}), ); } } else if (data.status === 'failed') { const meta = {friendly_name: resolvedEntity.name}; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `pairing`, message: 'interview_failed', meta}), + stringify({type: `pairing`, message: 'interview_failed', meta}), ); } else { /* istanbul ignore else */ @@ -424,13 +425,13 @@ class BridgeLegacy extends Extension { const meta = {friendly_name: resolvedEntity.name}; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `pairing`, message: 'interview_started', meta}), + stringify({type: `pairing`, message: 'interview_started', meta}), ); } } } else if (type === 'deviceAnnounce') { const meta = {friendly_name: resolvedEntity.name}; - this.mqtt.publish('bridge/log', JSON.stringify({type: `device_announced`, message: 'announce', meta})); + this.mqtt.publish('bridge/log', stringify({type: `device_announced`, message: 'announce', meta})); } else { /* istanbul ignore else */ if (type === 'deviceLeave') { @@ -438,7 +439,7 @@ class BridgeLegacy extends Extension { const meta = {friendly_name: name}; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `device_removed`, message: 'left_network', meta}), + stringify({type: `device_removed`, message: 'left_network', meta}), ); } } @@ -448,7 +449,7 @@ class BridgeLegacy extends Extension { logger.info('Starting touchlink factory reset...'); this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `touchlink`, message: 'reset_started', meta: {status: 'started'}}), + stringify({type: `touchlink`, message: 'reset_started', meta: {status: 'started'}}), ); const result = await this.zigbee.touchlinkFactoryReset(); @@ -456,13 +457,13 @@ class BridgeLegacy extends Extension { logger.info('Successfully factory reset device through Touchlink'); this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `touchlink`, message: 'reset_success', meta: {status: 'success'}}), + stringify({type: `touchlink`, message: 'reset_success', meta: {status: 'success'}}), ); } else { logger.warn('Failed to factory reset device through Touchlink'); this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `touchlink`, message: 'reset_failed', meta: {status: 'failed'}}), + stringify({type: `touchlink`, message: 'reset_failed', meta: {status: 'failed'}}), ); } } diff --git a/lib/extension/networkMap.js b/lib/extension/networkMap.js index b8750d34..cb0dc55d 100644 --- a/lib/extension/networkMap.js +++ b/lib/extension/networkMap.js @@ -2,6 +2,7 @@ const settings = require('../util/settings'); const utils = require('../util/utils'); const logger = require('../util/logger'); const Extension = require('./extension'); +const stringify = require('json-stable-stringify'); /** * This extension creates a network map @@ -65,19 +66,19 @@ class NetworkMap extends Extension { const value = this.supportedFormats[type](topology); await this.mqtt.publish( 'bridge/response/networkmap', - JSON.stringify(utils.getResponse(message, {routes, type, value}, null)), + stringify(utils.getResponse(message, {routes, type, value}, null)), ); } catch (error) { await this.mqtt.publish( 'bridge/response/networkmap', - JSON.stringify(utils.getResponse(message, {}, error.message)), + stringify(utils.getResponse(message, {}, error.message)), ); } } } raw(topology) { - return JSON.stringify(topology); + return stringify(topology); } graphviz(topology) { diff --git a/lib/extension/otaUpdate.js b/lib/extension/otaUpdate.js index 383b65a0..637de825 100644 --- a/lib/extension/otaUpdate.js +++ b/lib/extension/otaUpdate.js @@ -1,5 +1,6 @@ const settings = require('../util/settings'); const logger = require('../util/logger'); +const stringify = require('json-stable-stringify'); const utils = require('../util/utils'); const legacyTopicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/ota_update/.+$`); const topicRegex = @@ -67,7 +68,7 @@ class OTAUpdate extends Extension { const meta = {status: 'available', device: resolvedEntity.settings.friendly_name}; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `ota_update`, message, meta}), + stringify({type: `ota_update`, message, meta}), ); } } @@ -140,7 +141,7 @@ class OTAUpdate extends Extension { const meta = {status: `not_supported`, device: resolvedEntity.name}; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `ota_update`, message: error, meta}), + stringify({type: `ota_update`, message: error, meta}), ); } } else if (this.inProgress.has(resolvedEntity.device.ieeeAddr)) { @@ -157,7 +158,7 @@ class OTAUpdate extends Extension { const meta = {status: `checking_if_available`, device: resolvedEntity.name}; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `ota_update`, message: msg, meta}), + stringify({type: `ota_update`, message: msg, meta}), ); } @@ -173,7 +174,7 @@ class OTAUpdate extends Extension { const meta = {status: available ? 'available' : 'not_available', device: resolvedEntity.name}; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `ota_update`, message: msg, meta}), + stringify({type: `ota_update`, message: msg, meta}), ); } @@ -189,7 +190,7 @@ class OTAUpdate extends Extension { const meta = {status: `check_failed`, device: resolvedEntity.name}; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `ota_update`, message: error, meta}), + stringify({type: `ota_update`, message: error, meta}), ); } } @@ -202,7 +203,7 @@ class OTAUpdate extends Extension { const meta = {status: `update_in_progress`, device: resolvedEntity.name}; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `ota_update`, msg, meta}), + stringify({type: `ota_update`, msg, meta}), ); } @@ -221,14 +222,14 @@ class OTAUpdate extends Extension { /* istanbul ignore else */ if (settings.get().advanced.legacy_api) { const meta = {status: `update_progress`, device: resolvedEntity.name, progress}; - this.mqtt.publish('bridge/log', JSON.stringify({type: `ota_update`, message: msg, meta})); + this.mqtt.publish('bridge/log', stringify({type: `ota_update`, message: msg, meta})); } }; const from_ = await this.readSoftwareBuildIDAndDateCode(resolvedEntity.device, false); await resolvedEntity.definition.ota.updateToLatest(resolvedEntity.device, logger, onProgress); const to = await this.readSoftwareBuildIDAndDateCode(resolvedEntity.device, true); - const [fromS, toS] = [JSON.stringify(from_), JSON.stringify(to)]; + const [fromS, toS] = [stringify(from_), stringify(to)]; const msg = `Finished update of '${resolvedEntity.name}'` + (to ? `, from '${fromS}' to '${toS}'` : ``); logger.info(msg); @@ -241,7 +242,7 @@ class OTAUpdate extends Extension { /* istanbul ignore else */ if (settings.get().advanced.legacy_api) { const meta = {status: `update_succeeded`, device: resolvedEntity.name, from: from_, to}; - this.mqtt.publish('bridge/log', JSON.stringify({type: `ota_update`, message, meta})); + this.mqtt.publish('bridge/log', stringify({type: `ota_update`, message, meta})); } } catch (e) { error = `Update of '${resolvedEntity.name}' failed (${e.message})`; @@ -253,7 +254,7 @@ class OTAUpdate extends Extension { /* istanbul ignore else */ if (settings.get().advanced.legacy_api) { const meta = {status: `update_failed`, device: resolvedEntity.name}; - this.mqtt.publish('bridge/log', JSON.stringify({type: `ota_update`, message: error, meta})); + this.mqtt.publish('bridge/log', stringify({type: `ota_update`, message: error, meta})); } } } @@ -264,7 +265,7 @@ class OTAUpdate extends Extension { const triggeredViaLegacyApi = topic.match(legacyTopicRegex); if (!triggeredViaLegacyApi) { const response = utils.getResponse(message, responseData, error); - await this.mqtt.publish(`bridge/response/device/ota_update/${type}`, JSON.stringify(response)); + await this.mqtt.publish(`bridge/response/device/ota_update/${type}`, stringify(response)); } if (error) { diff --git a/lib/extension/publish.js b/lib/extension/publish.js index 2d337f9b..7ba08c8b 100644 --- a/lib/extension/publish.js +++ b/lib/extension/publish.js @@ -5,6 +5,7 @@ const logger = require('../util/logger'); const utils = require('../util/utils'); const assert = require('assert'); const Extension = require('./extension'); +const stringify = require('json-stable-stringify'); const topicRegex = new RegExp(`^(.+?)(?:/(${utils.getEndpointNames().join('|')}))?/(get|set)(?:/(.+))?`); @@ -64,7 +65,7 @@ class EntityPublish extends Extension { const message = {friendly_name: entityKey}; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `entity_not_found`, message}), + stringify({type: `entity_not_found`, message}), ); } @@ -245,7 +246,7 @@ class EntityPublish extends Extension { const meta = {friendly_name: resolvedEntity.name}; this.mqtt.publish( 'bridge/log', - JSON.stringify({type: `zigbee_publish_error`, message, meta}), + stringify({type: `zigbee_publish_error`, message, meta}), ); } } diff --git a/lib/extension/receive.js b/lib/extension/receive.js index 1bea843f..93942bb9 100755 --- a/lib/extension/receive.js +++ b/lib/extension/receive.js @@ -3,6 +3,7 @@ const logger = require('../util/logger'); const utils = require('../util/utils'); const debounce = require('debounce'); const Extension = require('./extension'); +const stringify = require('json-stable-stringify'); class Receive extends Extension { constructor(zigbee, mqtt, state, publishEntityState, eventBus) { @@ -125,7 +126,7 @@ class Receive extends Extension { if (!converters.length && data.cluster !== 'genOta' && data.cluster !== 'genTime') { logger.debug( `No converter available for '${resolvedEntity.definition.model}' with cluster '${data.cluster}' ` + - `and type '${data.type}' and data '${JSON.stringify(data.data)}'`, + `and type '${data.type}' and data '${stringify(data.data)}'`, ); return; } diff --git a/lib/state.js b/lib/state.js index 02ce3d31..a54828c3 100644 --- a/lib/state.js +++ b/lib/state.js @@ -3,6 +3,7 @@ const data = require('./util/data'); const settings = require('./util/settings'); const fs = require('fs'); const objectAssignDeep = require('object-assign-deep'); +const stringify = require('json-stable-stringify'); const saveInterval = 1000 * 60 * 5; // 5 minutes @@ -56,7 +57,7 @@ class State { save() { if (settings.get().advanced.cache_state_persistent) { logger.debug(`Saving state to file ${this.file}`); - const json = JSON.stringify(this.state, null, 4); + const json = stringify(this.state, null, 4); fs.writeFileSync(this.file, json, 'utf8'); } else { logger.debug(`Not saving state`); diff --git a/lib/zigbee.js b/lib/zigbee.js index 2a94f377..137efddd 100644 --- a/lib/zigbee.js +++ b/lib/zigbee.js @@ -7,6 +7,7 @@ const utils = require('./util/utils'); const events = require('events'); const objectAssignDeep = require('object-assign-deep'); const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters'); +const stringify = require('json-stable-stringify'); const endpointNames = utils.getEndpointNames(); const keyEndpointByNumber = new RegExp(`.*/([0-9]*)$`); @@ -42,7 +43,7 @@ class Zigbee extends events.EventEmitter { const herdsmanSettingsLog = objectAssignDeep.noMutate(herdsmanSettings); herdsmanSettingsLog.network.networkKey = 'HIDDEN'; - logger.debug(`Using zigbee-herdsman with settings: '${JSON.stringify(herdsmanSettingsLog)}'`); + logger.debug(`Using zigbee-herdsman with settings: '${stringify(herdsmanSettingsLog)}'`); if (herdsmanSettings.network.networkKey === 'GENERATE') { const newKey = Array.from({length: 16}, () => Math.floor(Math.random() * 255)); @@ -67,8 +68,8 @@ class Zigbee extends events.EventEmitter { this.herdsman.on('message', (data) => this.emit('event', 'message', data)); logger.info('zigbee-herdsman started'); - logger.info(`Coordinator firmware version: '${JSON.stringify(await this.getCoordinatorVersion())}'`); - logger.debug(`Zigbee network parameters: ${JSON.stringify(await this.herdsman.getNetworkParameters())}`); + logger.info(`Coordinator firmware version: '${stringify(await this.getCoordinatorVersion())}'`); + logger.debug(`Zigbee network parameters: ${stringify(await this.herdsman.getNetworkParameters())}`); for (const device of this.getClients()) { // If a passlist is used, all other device will be removed from the network. diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 78d36fa6..a64277ac 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -416,48 +416,48 @@ "dev": true }, "@jest/console": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.2.0.tgz", - "integrity": "sha512-mXQfx3nSLwiHm1i7jbu+uvi+vvpVjNGzIQYLCfsat9rapC+MJkS4zBseNrgJE0vU921b3P67bQzhduphjY3Tig==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.3.0.tgz", + "integrity": "sha512-/5Pn6sJev0nPUcAdpJHMVIsA8sKizL2ZkcKPE5+dJrCccks7tcM7c9wbgHudBJbxXLoTbqsHkG1Dofoem4F09w==", "dev": true, "requires": { - "@jest/types": "^26.2.0", + "@jest/types": "^26.3.0", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^26.2.0", - "jest-util": "^26.2.0", + "jest-message-util": "^26.3.0", + "jest-util": "^26.3.0", "slash": "^3.0.0" } }, "@jest/core": { - "version": "26.2.2", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.2.2.tgz", - "integrity": "sha512-UwA8gNI8aeV4FHGfGAUfO/DHjrFVvlBravF1Tm9Kt6qFE+6YHR47kFhgdepOFpADEKstyO+MVdPvkV6/dyt9sA==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.4.0.tgz", + "integrity": "sha512-mpXm4OjWQbz7qbzGIiSqvfNZ1FxX6ywWgLtdSD2luPORt5zKPtqcdDnX7L8RdfMaj1znDBgN2+gB094ZIr7vnA==", "dev": true, "requires": { - "@jest/console": "^26.2.0", - "@jest/reporters": "^26.2.2", - "@jest/test-result": "^26.2.0", - "@jest/transform": "^26.2.2", - "@jest/types": "^26.2.0", + "@jest/console": "^26.3.0", + "@jest/reporters": "^26.4.0", + "@jest/test-result": "^26.3.0", + "@jest/transform": "^26.3.0", + "@jest/types": "^26.3.0", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.4", - "jest-changed-files": "^26.2.0", - "jest-config": "^26.2.2", - "jest-haste-map": "^26.2.2", - "jest-message-util": "^26.2.0", + "jest-changed-files": "^26.3.0", + "jest-config": "^26.4.0", + "jest-haste-map": "^26.3.0", + "jest-message-util": "^26.3.0", "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.2.2", - "jest-resolve-dependencies": "^26.2.2", - "jest-runner": "^26.2.2", - "jest-runtime": "^26.2.2", - "jest-snapshot": "^26.2.2", - "jest-util": "^26.2.0", - "jest-validate": "^26.2.0", - "jest-watcher": "^26.2.0", + "jest-resolve": "^26.4.0", + "jest-resolve-dependencies": "^26.4.0", + "jest-runner": "^26.4.0", + "jest-runtime": "^26.4.0", + "jest-snapshot": "^26.4.0", + "jest-util": "^26.3.0", + "jest-validate": "^26.4.0", + "jest-watcher": "^26.3.0", "micromatch": "^4.0.2", "p-each-series": "^2.1.0", "rimraf": "^3.0.0", @@ -466,53 +466,53 @@ } }, "@jest/environment": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.2.0.tgz", - "integrity": "sha512-oCgp9NmEiJ5rbq9VI/v/yYLDpladAAVvFxZgNsnJxOETuzPZ0ZcKKHYjKYwCtPOP1WCrM5nmyuOhMStXFGHn+g==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.3.0.tgz", + "integrity": "sha512-EW+MFEo0DGHahf83RAaiqQx688qpXgl99wdb8Fy67ybyzHwR1a58LHcO376xQJHfmoXTu89M09dH3J509cx2AA==", "dev": true, "requires": { - "@jest/fake-timers": "^26.2.0", - "@jest/types": "^26.2.0", + "@jest/fake-timers": "^26.3.0", + "@jest/types": "^26.3.0", "@types/node": "*", - "jest-mock": "^26.2.0" + "jest-mock": "^26.3.0" } }, "@jest/fake-timers": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.2.0.tgz", - "integrity": "sha512-45Gfe7YzYTKqTayBrEdAF0qYyAsNRBzfkV0IyVUm3cx7AsCWlnjilBM4T40w7IXT5VspOgMPikQlV0M6gHwy/g==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.3.0.tgz", + "integrity": "sha512-ZL9ytUiRwVP8ujfRepffokBvD2KbxbqMhrXSBhSdAhISCw3gOkuntisiSFv+A6HN0n0fF4cxzICEKZENLmW+1A==", "dev": true, "requires": { - "@jest/types": "^26.2.0", + "@jest/types": "^26.3.0", "@sinonjs/fake-timers": "^6.0.1", "@types/node": "*", - "jest-message-util": "^26.2.0", - "jest-mock": "^26.2.0", - "jest-util": "^26.2.0" + "jest-message-util": "^26.3.0", + "jest-mock": "^26.3.0", + "jest-util": "^26.3.0" } }, "@jest/globals": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.2.0.tgz", - "integrity": "sha512-Hoc6ScEIPaym7RNytIL2ILSUWIGKlwEv+JNFof9dGYOdvPjb2evEURSslvCMkNuNg1ECEClTE8PH7ULlMJntYA==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.4.0.tgz", + "integrity": "sha512-QKwoVAeL9d0xaEM9ebPvfc+bolN04F+o3zM2jswGDBiiNjCogZ3LvOaqumRdDyz6kLmbx+UhgMBAVuLunbXZ2A==", "dev": true, "requires": { - "@jest/environment": "^26.2.0", - "@jest/types": "^26.2.0", - "expect": "^26.2.0" + "@jest/environment": "^26.3.0", + "@jest/types": "^26.3.0", + "expect": "^26.4.0" } }, "@jest/reporters": { - "version": "26.2.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.2.2.tgz", - "integrity": "sha512-7854GPbdFTAorWVh+RNHyPO9waRIN6TcvCezKVxI1khvFq9YjINTW7J3WU+tbR038Ynn6WjYred6vtT0YmIWVQ==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.4.0.tgz", + "integrity": "sha512-14OPAAuYhgRBSNxAocVluX6ksdMdK/EuP9NmtBXU9g1uKaVBrPnohn/CVm6iMot1a9iU8BCxa5715YRf8FEg/A==", "dev": true, "requires": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.2.0", - "@jest/test-result": "^26.2.0", - "@jest/transform": "^26.2.2", - "@jest/types": "^26.2.0", + "@jest/console": "^26.3.0", + "@jest/test-result": "^26.3.0", + "@jest/transform": "^26.3.0", + "@jest/types": "^26.3.0", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", @@ -523,22 +523,22 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.2.2", - "jest-resolve": "^26.2.2", - "jest-util": "^26.2.0", - "jest-worker": "^26.2.1", + "jest-haste-map": "^26.3.0", + "jest-resolve": "^26.4.0", + "jest-util": "^26.3.0", + "jest-worker": "^26.3.0", "node-notifier": "^7.0.0", "slash": "^3.0.0", "source-map": "^0.6.0", "string-length": "^4.0.1", "terminal-link": "^2.0.0", - "v8-to-istanbul": "^4.1.3" + "v8-to-istanbul": "^5.0.1" } }, "@jest/source-map": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.1.0.tgz", - "integrity": "sha512-XYRPYx4eEVX15cMT9mstnO7hkHP3krNtKfxUYd8L7gbtia8JvZZ6bMzSwa6IQJENbudTwKMw5R1BePRD+bkEmA==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.3.0.tgz", + "integrity": "sha512-hWX5IHmMDWe1kyrKl7IhFwqOuAreIwHhbe44+XH2ZRHjrKIh0LO5eLQ/vxHFeAfRwJapmxuqlGAEYLadDq6ZGQ==", "dev": true, "requires": { "callsites": "^3.0.0", @@ -547,46 +547,46 @@ } }, "@jest/test-result": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.2.0.tgz", - "integrity": "sha512-kgPlmcVafpmfyQEu36HClK+CWI6wIaAWDHNxfQtGuKsgoa2uQAYdlxjMDBEa3CvI40+2U3v36gQF6oZBkoKatw==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.3.0.tgz", + "integrity": "sha512-a8rbLqzW/q7HWheFVMtghXV79Xk+GWwOK1FrtimpI5n1la2SY0qHri3/b0/1F0Ve0/yJmV8pEhxDfVwiUBGtgg==", "dev": true, "requires": { - "@jest/console": "^26.2.0", - "@jest/types": "^26.2.0", + "@jest/console": "^26.3.0", + "@jest/types": "^26.3.0", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "@jest/test-sequencer": { - "version": "26.2.2", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.2.2.tgz", - "integrity": "sha512-SliZWon5LNqV/lVXkeowSU6L8++FGOu3f43T01L1Gv6wnFDP00ER0utV9jyK9dVNdXqfMNCN66sfcyar/o7BNw==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.4.0.tgz", + "integrity": "sha512-9Z7lCShS7vERp+DRwIVNH/6sHMWwJK1DPnGCpGeVLGJJWJ4Y08sQI3vIKdmKHu2KmwlUBpRM+BFf7NlVUkl5XA==", "dev": true, "requires": { - "@jest/test-result": "^26.2.0", + "@jest/test-result": "^26.3.0", "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.2.2", - "jest-runner": "^26.2.2", - "jest-runtime": "^26.2.2" + "jest-haste-map": "^26.3.0", + "jest-runner": "^26.4.0", + "jest-runtime": "^26.4.0" } }, "@jest/transform": { - "version": "26.2.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.2.2.tgz", - "integrity": "sha512-c1snhvi5wRVre1XyoO3Eef5SEWpuBCH/cEbntBUd9tI5sNYiBDmO0My/lc5IuuGYKp/HFIHV1eZpSx5yjdkhKw==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.3.0.tgz", + "integrity": "sha512-Isj6NB68QorGoFWvcOjlUhpkT56PqNIsXKR7XfvoDlCANn/IANlh8DrKAA2l2JKC3yWSMH5wS0GwuQM20w3b2A==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/types": "^26.2.0", + "@jest/types": "^26.3.0", "babel-plugin-istanbul": "^6.0.0", "chalk": "^4.0.0", "convert-source-map": "^1.4.0", "fast-json-stable-stringify": "^2.0.0", "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.2.2", + "jest-haste-map": "^26.3.0", "jest-regex-util": "^26.0.0", - "jest-util": "^26.2.0", + "jest-util": "^26.3.0", "micromatch": "^4.0.2", "pirates": "^4.0.1", "slash": "^3.0.0", @@ -595,13 +595,13 @@ } }, "@jest/types": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.2.0.tgz", - "integrity": "sha512-lvm3rJvctxd7+wxKSxxbzpDbr4FXDLaC57WEKdUIZ2cjTYuxYSc0zlyD7Z4Uqr5VdKxRUrtwIkiqBuvgf8uKJA==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz", + "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", + "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^15.0.0", "chalk": "^4.0.0" @@ -697,12 +697,11 @@ } }, "@types/istanbul-reports": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", - "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", "dev": true, "requires": { - "@types/istanbul-lib-coverage": "*", "@types/istanbul-lib-report": "*" } }, @@ -946,11 +945,6 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -970,22 +964,22 @@ "dev": true }, "aws4": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", - "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==", "dev": true }, "babel-jest": { - "version": "26.2.2", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.2.2.tgz", - "integrity": "sha512-JmLuePHgA+DSOdOL8lPxCgD2LhPPm+rdw1vnxR73PpIrnmKCS2/aBhtkAcxQWuUcW2hBrH8MJ3LKXE7aWpNZyA==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.3.0.tgz", + "integrity": "sha512-sxPnQGEyHAOPF8NcUsD0g7hDCnvLL2XyblRBcgrzTWBB/mAIpWow3n1bEL+VghnnZfreLhFSBsFluRoK2tRK4g==", "dev": true, "requires": { - "@jest/transform": "^26.2.2", - "@jest/types": "^26.2.0", + "@jest/transform": "^26.3.0", + "@jest/types": "^26.3.0", "@types/babel__core": "^7.1.7", "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^26.2.0", + "babel-preset-jest": "^26.3.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "slash": "^3.0.0" @@ -1036,13 +1030,13 @@ } }, "babel-preset-jest": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.2.0.tgz", - "integrity": "sha512-R1k8kdP3R9phYQugXeNnK/nvCGlBzG4m3EoIIukC80GXb6wCv2XiwPhK6K9MAkQcMszWBYvl2Wm+yigyXFQqXg==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.3.0.tgz", + "integrity": "sha512-5WPdf7nyYi2/eRxCbVrE1kKCWxgWY4RsPEbdJWFm7QsesFGqjdkyLeu1zRkwM1cxK6EPIlNd6d2AxLk7J+t4pw==", "dev": true, "requires": { "babel-plugin-jest-hoist": "^26.2.0", - "babel-preset-current-node-syntax": "^0.1.2" + "babel-preset-current-node-syntax": "^0.1.3" } }, "balanced-match": { @@ -1638,9 +1632,9 @@ "dev": true }, "diff-sequences": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.0.0.tgz", - "integrity": "sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.3.0.tgz", + "integrity": "sha512-5j5vdRcw3CNctePNYN0Wy2e/JbWT6cAYnXv5OuqPhDpyCGc0uLu2TK0zOCJWNB9kOIfYMSpIulRaDgIi4HJ6Ig==", "dev": true }, "doctrine": { @@ -2168,16 +2162,16 @@ } }, "expect": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.2.0.tgz", - "integrity": "sha512-8AMBQ9UVcoUXt0B7v+5/U5H6yiUR87L6eKCfjE3spx7Ya5lF+ebUo37MCFBML2OiLfkX1sxmQOZhIDonyVTkcw==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-26.4.0.tgz", + "integrity": "sha512-dbYDJhFcqQsamlos6nEwAMe+ahdckJBk5fmw1DYGLQGabGSlUuT+Fm2jHYw5119zG3uIhP+lCQbjJhFEdZMJtg==", "dev": true, "requires": { - "@jest/types": "^26.2.0", + "@jest/types": "^26.3.0", "ansi-styles": "^4.0.0", - "jest-get-type": "^26.0.0", - "jest-matcher-utils": "^26.2.0", - "jest-message-util": "^26.2.0", + "jest-get-type": "^26.3.0", + "jest-matcher-utils": "^26.4.0", + "jest-message-util": "^26.3.0", "jest-regex-util": "^26.0.0" }, "dependencies": { @@ -3067,33 +3061,33 @@ } }, "jest": { - "version": "26.2.2", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.2.2.tgz", - "integrity": "sha512-EkJNyHiAG1+A8pqSz7cXttoVa34hOEzN/MrnJhYnfp5VHxflVcf2pu3oJSrhiy6LfIutLdWo+n6q63tjcoIeig==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-26.4.0.tgz", + "integrity": "sha512-lNCOS+ckRHE1wFyVtQClBmbsOVuH2GWUTJMDL3vunp9DXcah+V8vfvVVApngClcdoc3rgZpqOfCNKLjxjj2l4g==", "dev": true, "requires": { - "@jest/core": "^26.2.2", + "@jest/core": "^26.4.0", "import-local": "^3.0.2", - "jest-cli": "^26.2.2" + "jest-cli": "^26.4.0" }, "dependencies": { "jest-cli": { - "version": "26.2.2", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.2.2.tgz", - "integrity": "sha512-vVcly0n/ijZvdy6gPQiQt0YANwX2hLTPQZHtW7Vi3gcFdKTtif7YpI85F8R8JYy5DFSWz4x1OW0arnxlziu5Lw==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.4.0.tgz", + "integrity": "sha512-kw2Pr3V2x9/WzSDGsbz/MJBNlCoPMxMudrIavft4bqRlv5tASjU51tyO+1Os1LdW2dAnLQZYsxFUZ8oWPyssGQ==", "dev": true, "requires": { - "@jest/core": "^26.2.2", - "@jest/test-result": "^26.2.0", - "@jest/types": "^26.2.0", + "@jest/core": "^26.4.0", + "@jest/test-result": "^26.3.0", + "@jest/types": "^26.3.0", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.4", "import-local": "^3.0.2", "is-ci": "^2.0.0", - "jest-config": "^26.2.2", - "jest-util": "^26.2.0", - "jest-validate": "^26.2.0", + "jest-config": "^26.4.0", + "jest-util": "^26.3.0", + "jest-validate": "^26.4.0", "prompts": "^2.0.1", "yargs": "^15.3.1" } @@ -3101,12 +3095,12 @@ } }, "jest-changed-files": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.2.0.tgz", - "integrity": "sha512-+RyJb+F1K/XBLIYiL449vo5D+CvlHv29QveJUWNPXuUicyZcq+tf1wNxmmFeRvAU1+TzhwqczSjxnCCFt7+8iA==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.3.0.tgz", + "integrity": "sha512-1C4R4nijgPltX6fugKxM4oQ18zimS7LqQ+zTTY8lMCMFPrxqBFb7KJH0Z2fRQJvw2Slbaipsqq7s1mgX5Iot+g==", "dev": true, "requires": { - "@jest/types": "^26.2.0", + "@jest/types": "^26.3.0", "execa": "^4.0.0", "throat": "^5.0.0" }, @@ -3129,9 +3123,9 @@ } }, "get-stream": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", - "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "requires": { "pump": "^3.0.0" @@ -3149,41 +3143,41 @@ } }, "jest-config": { - "version": "26.2.2", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.2.2.tgz", - "integrity": "sha512-2lhxH0y4YFOijMJ65usuf78m7+9/8+hAb1PZQtdRdgnQpAb4zP6KcVDDktpHEkspBKnc2lmFu+RQdHukUUbiTg==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.4.0.tgz", + "integrity": "sha512-MxsvrBug8YY+C4QcUBtmgnHyFeW7w3Ouk/w9eplCDN8VJGVyBEZFe8Lxzfp2pSqh0Dqurqv8Oik2YkbekGUlxg==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.2.2", - "@jest/types": "^26.2.0", - "babel-jest": "^26.2.2", + "@jest/test-sequencer": "^26.4.0", + "@jest/types": "^26.3.0", + "babel-jest": "^26.3.0", "chalk": "^4.0.0", "deepmerge": "^4.2.2", "glob": "^7.1.1", "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.2.0", - "jest-environment-node": "^26.2.0", - "jest-get-type": "^26.0.0", - "jest-jasmine2": "^26.2.2", + "jest-environment-jsdom": "^26.3.0", + "jest-environment-node": "^26.3.0", + "jest-get-type": "^26.3.0", + "jest-jasmine2": "^26.4.0", "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.2.2", - "jest-util": "^26.2.0", - "jest-validate": "^26.2.0", + "jest-resolve": "^26.4.0", + "jest-util": "^26.3.0", + "jest-validate": "^26.4.0", "micromatch": "^4.0.2", - "pretty-format": "^26.2.0" + "pretty-format": "^26.4.0" } }, "jest-diff": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.2.0.tgz", - "integrity": "sha512-Wu4Aopi2nzCsHWLBlD48TgRy3Z7OsxlwvHNd1YSnHc7q1NJfrmyCPoUXrTIrydQOG5ApaYpsAsdfnMbJqV1/wQ==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.4.0.tgz", + "integrity": "sha512-wwC38HlOW+iTq6j5tkj/ZamHn6/nrdcEOc/fKaVILNtN2NLWGdkfRaHWwfNYr5ehaLvuoG2LfCZIcWByVj0gjg==", "dev": true, "requires": { "chalk": "^4.0.0", - "diff-sequences": "^26.0.0", - "jest-get-type": "^26.0.0", - "pretty-format": "^26.2.0" + "diff-sequences": "^26.3.0", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.4.0" } }, "jest-docblock": { @@ -3196,60 +3190,60 @@ } }, "jest-each": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.2.0.tgz", - "integrity": "sha512-gHPCaho1twWHB5bpcfnozlc6mrMi+VAewVPNgmwf81x2Gzr6XO4dl+eOrwPWxbkYlgjgrYjWK2xgKnixbzH3Ew==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.4.0.tgz", + "integrity": "sha512-+cyBh1ehs6thVT/bsZVG+WwmRn2ix4Q4noS9yLZgM10yGWPW12/TDvwuOV2VZXn1gi09/ZwJKJWql6YW1C9zNw==", "dev": true, "requires": { - "@jest/types": "^26.2.0", + "@jest/types": "^26.3.0", "chalk": "^4.0.0", - "jest-get-type": "^26.0.0", - "jest-util": "^26.2.0", - "pretty-format": "^26.2.0" + "jest-get-type": "^26.3.0", + "jest-util": "^26.3.0", + "pretty-format": "^26.4.0" } }, "jest-environment-jsdom": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.2.0.tgz", - "integrity": "sha512-sDG24+5M4NuIGzkI3rJW8XUlrpkvIdE9Zz4jhD8OBnVxAw+Y1jUk9X+lAOD48nlfUTlnt3lbAI3k2Ox+WF3S0g==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.3.0.tgz", + "integrity": "sha512-zra8He2btIMJkAzvLaiZ9QwEPGEetbxqmjEBQwhH3CA+Hhhu0jSiEJxnJMbX28TGUvPLxBt/zyaTLrOPF4yMJA==", "dev": true, "requires": { - "@jest/environment": "^26.2.0", - "@jest/fake-timers": "^26.2.0", - "@jest/types": "^26.2.0", + "@jest/environment": "^26.3.0", + "@jest/fake-timers": "^26.3.0", + "@jest/types": "^26.3.0", "@types/node": "*", - "jest-mock": "^26.2.0", - "jest-util": "^26.2.0", + "jest-mock": "^26.3.0", + "jest-util": "^26.3.0", "jsdom": "^16.2.2" } }, "jest-environment-node": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.2.0.tgz", - "integrity": "sha512-4M5ExTYkJ19efBzkiXtBi74JqKLDciEk4CEsp5tTjWGYMrlKFQFtwIVG3tW1OGE0AlXhZjuHPwubuRYY4j4uOw==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.3.0.tgz", + "integrity": "sha512-c9BvYoo+FGcMj5FunbBgtBnbR5qk3uky8PKyRVpSfe2/8+LrNQMiXX53z6q2kY+j15SkjQCOSL/6LHnCPLVHNw==", "dev": true, "requires": { - "@jest/environment": "^26.2.0", - "@jest/fake-timers": "^26.2.0", - "@jest/types": "^26.2.0", + "@jest/environment": "^26.3.0", + "@jest/fake-timers": "^26.3.0", + "@jest/types": "^26.3.0", "@types/node": "*", - "jest-mock": "^26.2.0", - "jest-util": "^26.2.0" + "jest-mock": "^26.3.0", + "jest-util": "^26.3.0" } }, "jest-get-type": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.0.0.tgz", - "integrity": "sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", + "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", "dev": true }, "jest-haste-map": { - "version": "26.2.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.2.2.tgz", - "integrity": "sha512-3sJlMSt+NHnzCB+0KhJ1Ut4zKJBiJOlbrqEYNdRQGlXTv8kqzZWjUKQRY3pkjmlf+7rYjAV++MQ4D6g4DhAyOg==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.3.0.tgz", + "integrity": "sha512-DHWBpTJgJhLLGwE5Z1ZaqLTYqeODQIZpby0zMBsCU9iRFHYyhklYqP4EiG73j5dkbaAdSZhgB938mL51Q5LeZA==", "dev": true, "requires": { - "@jest/types": "^26.2.0", + "@jest/types": "^26.3.0", "@types/graceful-fs": "^4.1.2", "@types/node": "*", "anymatch": "^3.0.3", @@ -3257,70 +3251,70 @@ "fsevents": "^2.1.2", "graceful-fs": "^4.2.4", "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.2.0", - "jest-util": "^26.2.0", - "jest-worker": "^26.2.1", + "jest-serializer": "^26.3.0", + "jest-util": "^26.3.0", + "jest-worker": "^26.3.0", "micromatch": "^4.0.2", "sane": "^4.0.3", "walker": "^1.0.7" } }, "jest-jasmine2": { - "version": "26.2.2", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.2.2.tgz", - "integrity": "sha512-Q8AAHpbiZMVMy4Hz9j1j1bg2yUmPa1W9StBvcHqRaKa9PHaDUMwds8LwaDyzP/2fkybcTQE4+pTMDOG9826tEw==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.4.0.tgz", + "integrity": "sha512-cGBxwzDDKB09EPJ4pE69BMDv+2lO442IB1xQd+vL3cua2OKdeXQK6iDlQKoRX/iP0RgU5T8sn9yahLcx/+ox8Q==", "dev": true, "requires": { "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.2.0", - "@jest/source-map": "^26.1.0", - "@jest/test-result": "^26.2.0", - "@jest/types": "^26.2.0", + "@jest/environment": "^26.3.0", + "@jest/source-map": "^26.3.0", + "@jest/test-result": "^26.3.0", + "@jest/types": "^26.3.0", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "expect": "^26.2.0", + "expect": "^26.4.0", "is-generator-fn": "^2.0.0", - "jest-each": "^26.2.0", - "jest-matcher-utils": "^26.2.0", - "jest-message-util": "^26.2.0", - "jest-runtime": "^26.2.2", - "jest-snapshot": "^26.2.2", - "jest-util": "^26.2.0", - "pretty-format": "^26.2.0", + "jest-each": "^26.4.0", + "jest-matcher-utils": "^26.4.0", + "jest-message-util": "^26.3.0", + "jest-runtime": "^26.4.0", + "jest-snapshot": "^26.4.0", + "jest-util": "^26.3.0", + "pretty-format": "^26.4.0", "throat": "^5.0.0" } }, "jest-leak-detector": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.2.0.tgz", - "integrity": "sha512-aQdzTX1YiufkXA1teXZu5xXOJgy7wZQw6OJ0iH5CtQlOETe6gTSocaYKUNui1SzQ91xmqEUZ/WRavg9FD82rtQ==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.4.0.tgz", + "integrity": "sha512-7EXKKEKnAWUPyiVtGZzJflbPOtYUdlNoevNVOkAcPpdR8xWiYKPGNGA6sz25S+8YhZq3rmkQJYAh3/P0VnoRwA==", "dev": true, "requires": { - "jest-get-type": "^26.0.0", - "pretty-format": "^26.2.0" + "jest-get-type": "^26.3.0", + "pretty-format": "^26.4.0" } }, "jest-matcher-utils": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.2.0.tgz", - "integrity": "sha512-2cf/LW2VFb3ayPHrH36ZDjp9+CAeAe/pWBAwsV8t3dKcrINzXPVxq8qMWOxwt5BaeBCx4ZupVGH7VIgB8v66vQ==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.4.0.tgz", + "integrity": "sha512-u+xdCdq+F262DH+PutJKXLGr2H5P3DImdJCir51PGSfi3TtbLQ5tbzKaN8BkXbiTIU6ayuAYBWTlU1nyckVdzA==", "dev": true, "requires": { "chalk": "^4.0.0", - "jest-diff": "^26.2.0", - "jest-get-type": "^26.0.0", - "pretty-format": "^26.2.0" + "jest-diff": "^26.4.0", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.4.0" } }, "jest-message-util": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.2.0.tgz", - "integrity": "sha512-g362RhZaJuqeqG108n1sthz5vNpzTNy926eNDszo4ncRbmmcMRIUAZibnd6s5v2XSBCChAxQtCoN25gnzp7JbQ==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.3.0.tgz", + "integrity": "sha512-xIavRYqr4/otGOiLxLZGj3ieMmjcNE73Ui+LdSW/Y790j5acqCsAdDiLIbzHCZMpN07JOENRWX5DcU+OQ+TjTA==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.2.0", + "@jest/types": "^26.3.0", "@types/stack-utils": "^1.0.1", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", @@ -3330,12 +3324,12 @@ } }, "jest-mock": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.2.0.tgz", - "integrity": "sha512-XeC7yWtWmWByoyVOHSsE7NYsbXJLtJNgmhD7z4MKumKm6ET0si81bsSLbQ64L5saK3TgsHo2B/UqG5KNZ1Sp/Q==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.3.0.tgz", + "integrity": "sha512-PeaRrg8Dc6mnS35gOo/CbZovoDPKAeB1FICZiuagAgGvbWdNNyjQjkOaGUa/3N3JtpQ/Mh9P4A2D4Fv51NnP8Q==", "dev": true, "requires": { - "@jest/types": "^26.2.0", + "@jest/types": "^26.3.0", "@types/node": "*" } }, @@ -3352,98 +3346,98 @@ "dev": true }, "jest-resolve": { - "version": "26.2.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.2.2.tgz", - "integrity": "sha512-ye9Tj/ILn/0OgFPE/3dGpQPUqt4dHwIocxt5qSBkyzxQD8PbL0bVxBogX2FHxsd3zJA7V2H/cHXnBnNyyT9YoQ==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.4.0.tgz", + "integrity": "sha512-bn/JoZTEXRSlEx3+SfgZcJAVuTMOksYq9xe9O6s4Ekg84aKBObEaVXKOEilULRqviSLAYJldnoWV9c07kwtiCg==", "dev": true, "requires": { - "@jest/types": "^26.2.0", + "@jest/types": "^26.3.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.2.0", + "jest-util": "^26.3.0", "read-pkg-up": "^7.0.1", "resolve": "^1.17.0", "slash": "^3.0.0" } }, "jest-resolve-dependencies": { - "version": "26.2.2", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.2.2.tgz", - "integrity": "sha512-S5vufDmVbQXnpP7435gr710xeBGUFcKNpNswke7RmFvDQtmqPjPVU/rCeMlEU0p6vfpnjhwMYeaVjKZAy5QYJA==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.4.0.tgz", + "integrity": "sha512-hznK/hlrlhu8hwdbieRdHFKmcV83GW8t30libt/v6j1L3IEzb8iN21SaWzV8KRAAK4ijiU0kuge0wnHn+0rytQ==", "dev": true, "requires": { - "@jest/types": "^26.2.0", + "@jest/types": "^26.3.0", "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.2.2" + "jest-snapshot": "^26.4.0" } }, "jest-runner": { - "version": "26.2.2", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.2.2.tgz", - "integrity": "sha512-/qb6ptgX+KQ+aNMohJf1We695kaAfuu3u3ouh66TWfhTpLd9WbqcF6163d/tMoEY8GqPztXPLuyG0rHRVDLxCA==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.4.0.tgz", + "integrity": "sha512-XF+tnUGolnPriu6Gg+HHWftspMjD5NkTV2mQppQnpZe39GcUangJ0al7aBGtA3GbVAcRd048DQiJPmsQRdugjw==", "dev": true, "requires": { - "@jest/console": "^26.2.0", - "@jest/environment": "^26.2.0", - "@jest/test-result": "^26.2.0", - "@jest/types": "^26.2.0", + "@jest/console": "^26.3.0", + "@jest/environment": "^26.3.0", + "@jest/test-result": "^26.3.0", + "@jest/types": "^26.3.0", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.7.1", "exit": "^0.1.2", "graceful-fs": "^4.2.4", - "jest-config": "^26.2.2", + "jest-config": "^26.4.0", "jest-docblock": "^26.0.0", - "jest-haste-map": "^26.2.2", - "jest-leak-detector": "^26.2.0", - "jest-message-util": "^26.2.0", - "jest-resolve": "^26.2.2", - "jest-runtime": "^26.2.2", - "jest-util": "^26.2.0", - "jest-worker": "^26.2.1", + "jest-haste-map": "^26.3.0", + "jest-leak-detector": "^26.4.0", + "jest-message-util": "^26.3.0", + "jest-resolve": "^26.4.0", + "jest-runtime": "^26.4.0", + "jest-util": "^26.3.0", + "jest-worker": "^26.3.0", "source-map-support": "^0.5.6", "throat": "^5.0.0" } }, "jest-runtime": { - "version": "26.2.2", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.2.2.tgz", - "integrity": "sha512-a8VXM3DxCDnCIdl9+QucWFfQ28KdqmyVFqeKLigHdErtsx56O2ZIdQkhFSuP1XtVrG9nTNHbKxjh5XL1UaFDVQ==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.4.0.tgz", + "integrity": "sha512-1fjZgGpkyQBUTo59Vi19I4IcsBwzY6uwVFNjUmR06iIi3XRErkY28yimi4IUDRrofQErqcDEw2n3DF9WmQ6vEg==", "dev": true, "requires": { - "@jest/console": "^26.2.0", - "@jest/environment": "^26.2.0", - "@jest/fake-timers": "^26.2.0", - "@jest/globals": "^26.2.0", - "@jest/source-map": "^26.1.0", - "@jest/test-result": "^26.2.0", - "@jest/transform": "^26.2.2", - "@jest/types": "^26.2.0", + "@jest/console": "^26.3.0", + "@jest/environment": "^26.3.0", + "@jest/fake-timers": "^26.3.0", + "@jest/globals": "^26.4.0", + "@jest/source-map": "^26.3.0", + "@jest/test-result": "^26.3.0", + "@jest/transform": "^26.3.0", + "@jest/types": "^26.3.0", "@types/yargs": "^15.0.0", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.4", - "jest-config": "^26.2.2", - "jest-haste-map": "^26.2.2", - "jest-message-util": "^26.2.0", - "jest-mock": "^26.2.0", + "jest-config": "^26.4.0", + "jest-haste-map": "^26.3.0", + "jest-message-util": "^26.3.0", + "jest-mock": "^26.3.0", "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.2.2", - "jest-snapshot": "^26.2.2", - "jest-util": "^26.2.0", - "jest-validate": "^26.2.0", + "jest-resolve": "^26.4.0", + "jest-snapshot": "^26.4.0", + "jest-util": "^26.3.0", + "jest-validate": "^26.4.0", "slash": "^3.0.0", "strip-bom": "^4.0.0", "yargs": "^15.3.1" } }, "jest-serializer": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.2.0.tgz", - "integrity": "sha512-V7snZI9IVmyJEu0Qy0inmuXgnMWDtrsbV2p9CRAcmlmPVwpC2ZM8wXyYpiugDQnwLHx0V4+Pnog9Exb3UO8M6Q==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.3.0.tgz", + "integrity": "sha512-IDRBQBLPlKa4flg77fqg0n/pH87tcRKwe8zxOVTWISxGpPHYkRZ1dXKyh04JOja7gppc60+soKVZ791mruVdow==", "dev": true, "requires": { "@types/node": "*", @@ -3451,35 +3445,35 @@ } }, "jest-snapshot": { - "version": "26.2.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.2.2.tgz", - "integrity": "sha512-NdjD8aJS7ePu268Wy/n/aR1TUisG0BOY+QOW4f6h46UHEKOgYmmkvJhh2BqdVZQ0BHSxTMt04WpCf9njzx8KtA==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.4.0.tgz", + "integrity": "sha512-vFGmNGWHMBomrlOpheTMoqihymovuH3GqfmaEIWoPpsxUXyxT3IlbxI5I4m2vg0uv3HUJYg5JoGrkgMzVsAwCg==", "dev": true, "requires": { "@babel/types": "^7.0.0", - "@jest/types": "^26.2.0", + "@jest/types": "^26.3.0", "@types/prettier": "^2.0.0", "chalk": "^4.0.0", - "expect": "^26.2.0", + "expect": "^26.4.0", "graceful-fs": "^4.2.4", - "jest-diff": "^26.2.0", - "jest-get-type": "^26.0.0", - "jest-haste-map": "^26.2.2", - "jest-matcher-utils": "^26.2.0", - "jest-message-util": "^26.2.0", - "jest-resolve": "^26.2.2", + "jest-diff": "^26.4.0", + "jest-get-type": "^26.3.0", + "jest-haste-map": "^26.3.0", + "jest-matcher-utils": "^26.4.0", + "jest-message-util": "^26.3.0", + "jest-resolve": "^26.4.0", "natural-compare": "^1.4.0", - "pretty-format": "^26.2.0", + "pretty-format": "^26.4.0", "semver": "^7.3.2" } }, "jest-util": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.2.0.tgz", - "integrity": "sha512-YmDwJxLZ1kFxpxPfhSJ0rIkiZOM0PQbRcfH0TzJOhqCisCAsI1WcmoQqO83My9xeVA2k4n+rzg2UuexVKzPpig==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.3.0.tgz", + "integrity": "sha512-4zpn6bwV0+AMFN0IYhH/wnzIQzRaYVrz1A8sYnRnj4UXDXbOVtWmlaZkO9mipFqZ13okIfN87aDoJWB7VH6hcw==", "dev": true, "requires": { - "@jest/types": "^26.2.0", + "@jest/types": "^26.3.0", "@types/node": "*", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", @@ -3488,17 +3482,17 @@ } }, "jest-validate": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.2.0.tgz", - "integrity": "sha512-8XKn3hM6VIVmLNuyzYLCPsRCT83o8jMZYhbieh4dAyKLc4Ypr36rVKC+c8WMpWkfHHpGnEkvWUjjIAyobEIY/Q==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.4.0.tgz", + "integrity": "sha512-t56Z/FRMrLP6mpmje7/YgHy0wOzcuc6i3LBXz6kjmsUWYN62OuMdC86Vg9/dX59SvyitSqqegOrx+h7BkNXeaQ==", "dev": true, "requires": { - "@jest/types": "^26.2.0", + "@jest/types": "^26.3.0", "camelcase": "^6.0.0", "chalk": "^4.0.0", - "jest-get-type": "^26.0.0", + "jest-get-type": "^26.3.0", "leven": "^3.1.0", - "pretty-format": "^26.2.0" + "pretty-format": "^26.4.0" }, "dependencies": { "camelcase": { @@ -3516,24 +3510,24 @@ } }, "jest-watcher": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.2.0.tgz", - "integrity": "sha512-674Boco4Joe0CzgKPL6K4Z9LgyLx+ZvW2GilbpYb8rFEUkmDGgsZdv1Hv5rxsRpb1HLgKUOL/JfbttRCuFdZXQ==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.3.0.tgz", + "integrity": "sha512-XnLdKmyCGJ3VoF6G/p5ohbJ04q/vv5aH9ENI+i6BL0uu9WWB6Z7Z2lhQQk0d2AVZcRGp1yW+/TsoToMhBFPRdQ==", "dev": true, "requires": { - "@jest/test-result": "^26.2.0", - "@jest/types": "^26.2.0", + "@jest/test-result": "^26.3.0", + "@jest/types": "^26.3.0", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", - "jest-util": "^26.2.0", + "jest-util": "^26.3.0", "string-length": "^4.0.1" } }, "jest-worker": { - "version": "26.2.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.2.1.tgz", - "integrity": "sha512-+XcGMMJDTeEGncRb5M5Zq9P7K4sQ1sirhjdOxsN1462h6lFo9w59bl2LVQmdGEEeU3m+maZCkS2Tcc9SfCHO4A==", + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.3.0.tgz", + "integrity": "sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw==", "dev": true, "requires": { "@types/node": "*", @@ -3611,14 +3605,6 @@ "whatwg-url": "^8.0.0", "ws": "^7.2.3", "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "ws": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", - "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", - "dev": true - } } }, "jsesc": { @@ -3644,6 +3630,14 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "requires": { + "jsonify": "~0.0.0" + } + }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -3664,6 +3658,11 @@ "minimist": "^1.2.5" } }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -3724,9 +3723,9 @@ } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, "lodash.sortby": { @@ -3879,9 +3878,9 @@ "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" }, "mqtt": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-4.1.0.tgz", - "integrity": "sha512-dBihVZzaB8p9G/2ktSfamiaHmMnpCpP2du08317ZuEX1kBAbZOG9aMJQ11EChXnOX3GKUeiZYaSITueceQKT2A==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-4.2.0.tgz", + "integrity": "sha512-CGMLigAKCp0hknWPM15yuZRbVrfQjZCG9Wn3GXDEfY2chDmUi5GT3UQ3OhTtMi7fELYb2s4Q/QiWv+dECQ7ZQg==", "requires": { "base64-js": "^1.3.0", "commist": "^1.0.0", @@ -3891,13 +3890,13 @@ "es6-map": "^0.1.5", "help-me": "^1.0.1", "inherits": "^2.0.3", - "minimist": "^1.2.0", - "mqtt-packet": "^6.0.0", + "minimist": "^1.2.5", + "mqtt-packet": "^6.3.2", "pump": "^3.0.0", "readable-stream": "^2.3.6", "reinterval": "^1.1.0", "split2": "^3.1.0", - "websocket-stream": "^5.1.2", + "ws": "^7.3.1", "xtend": "^4.0.1" } }, @@ -4113,9 +4112,9 @@ } }, "onetime": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.1.tgz", - "integrity": "sha512-ZpZpjcJeugQfWsfyQlshVoowIIQ1qBGSVll4rfDq6JJVO//fesjoX808hXWfBjY+ROZgpKDI5TRSRBSoJiZ8eg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "requires": { "mimic-fn": "^2.1.0" @@ -4283,12 +4282,12 @@ "dev": true }, "pretty-format": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.2.0.tgz", - "integrity": "sha512-qi/8IuBu2clY9G7qCXgCdD1Bf9w+sXakdHTRToknzMtVy0g7c4MBWaZy7MfB7ndKZovRO6XRwJiAYqq+MC7SDA==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.4.0.tgz", + "integrity": "sha512-mEEwwpCseqrUtuMbrJG4b824877pM5xald3AkilJ47Po2YLr97/siejYQHqj2oDQBeJNbu+Q0qUuekJ8F0NAPg==", "dev": true, "requires": { - "@jest/types": "^26.2.0", + "@jest/types": "^26.3.0", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^16.12.0" @@ -5544,11 +5543,6 @@ "is-typedarray": "^1.0.0" } }, - "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" - }, "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", @@ -5664,9 +5658,9 @@ "dev": true }, "v8-to-istanbul": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-4.1.4.tgz", - "integrity": "sha512-Rw6vJHj1mbdK8edjR7+zuJrpDtKIgNdAvTSAcpYfgMIw+u2dPDntD3dgN4XQFLU2/fvFQdzj+EeSGfd/jnY5fQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-5.0.1.tgz", + "integrity": "sha512-mbDNjuDajqYe3TXFk5qxcQy8L1msXNE37WTlLoqqpBfRsimbNcrlhQlDPntmECEcUvdC+AQ8CyMMf6EUx1r74Q==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.1", @@ -5736,19 +5730,6 @@ "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", "dev": true }, - "websocket-stream": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/websocket-stream/-/websocket-stream-5.5.2.tgz", - "integrity": "sha512-8z49MKIHbGk3C4HtuHWDtYX8mYej1wWabjthC/RupM9ngeukU4IWoM46dgth1UOS/T4/IqgEdCDJuMe2039OQQ==", - "requires": { - "duplexify": "^3.5.1", - "inherits": "^2.0.1", - "readable-stream": "^2.3.3", - "safe-buffer": "^5.1.2", - "ws": "^3.2.0", - "xtend": "^4.0.0" - } - }, "whatwg-encoding": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", @@ -5938,14 +5919,9 @@ } }, "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", - "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", + "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" }, "xml-name-validator": { "version": "3.0.0", diff --git a/package.json b/package.json index 8fcde741..179f8e6e 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "git-last-commit": "^1.0.0", "humanize-duration": "^3.23.1", "js-yaml": "^3.14.0", + "json-stable-stringify": "^1.0.1", "mkdir-recursive": "^0.4.0", "moment": "^2.27.0", "mqtt": "^4.1.0", diff --git a/test/bind.test.js b/test/bind.test.js index 51fa07ca..ddb3b3db 100644 --- a/test/bind.test.js +++ b/test/bind.test.js @@ -5,6 +5,7 @@ const MQTT = require('./stub/mqtt'); const settings = require('../lib/util/settings'); const Controller = require('../lib/controller'); const flushPromises = () => new Promise(setImmediate); +const stringify = require('json-stable-stringify'); describe('Bind', () => { let controller; @@ -43,7 +44,7 @@ describe('Bind', () => { const target = zigbeeHerdsman.devices.bulb_color.getEndpoint(1); const endpoint = device.getEndpoint(1); mockClear(device); - MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote', to: 'bulb_color'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'bulb_color'})); await flushPromises(); expect(endpoint.bind).toHaveBeenCalledTimes(3); expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target); @@ -51,7 +52,7 @@ describe('Bind', () => { expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', - JSON.stringify({"data":{"from":"remote","to":"bulb_color","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}), + stringify({"data":{"from":"remote","to":"bulb_color","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -61,13 +62,13 @@ describe('Bind', () => { const target = zigbeeHerdsman.devices.bulb_color.getEndpoint(1); const endpoint = device.getEndpoint(1); mockClear(device); - MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote', to: 'bulb_color', clusters: ["genOnOff"]})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'bulb_color', clusters: ["genOnOff"]})); await flushPromises(); expect(endpoint.bind).toHaveBeenCalledTimes(1); expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', - JSON.stringify({"data":{"from":"remote","to":"bulb_color","clusters":["genOnOff"],"failed":[]},"status":"ok"}), + stringify({"data":{"from":"remote","to":"bulb_color","clusters":["genOnOff"],"failed":[]},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -77,12 +78,12 @@ describe('Bind', () => { const endpoint = device.getEndpoint(1); mockClear(device); logger.error.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote', to: 'button'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'button'})); await flushPromises(); expect(endpoint.bind).toHaveBeenCalledTimes(0); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', - JSON.stringify({"data":{"from":"remote","to":"button","clusters":[],"failed":[]},"status":"error","error":"Nothing to bind"}), + stringify({"data":{"from":"remote","to":"button","clusters":[],"failed":[]},"status":"error","error":"Nothing to bind"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -92,7 +93,7 @@ describe('Bind', () => { const target = zigbeeHerdsman.devices.bulb_color.getEndpoint(1); const endpoint = device.getEndpoint(1); mockClear(device); - MQTT.events.message('zigbee2mqtt/bridge/request/device/unbind', JSON.stringify({from: 'remote', to: 'bulb_color'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/unbind', stringify({from: 'remote', to: 'bulb_color'})); await flushPromises(); expect(endpoint.unbind).toHaveBeenCalledTimes(3); expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", target); @@ -100,7 +101,7 @@ describe('Bind', () => { expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", target); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/unbind', - JSON.stringify({"data":{"from":"remote","to":"bulb_color","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}), + stringify({"data":{"from":"remote","to":"bulb_color","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -111,7 +112,7 @@ describe('Bind', () => { const endpoint = device.getEndpoint(1); mockClear(device); endpoint.unbind.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/device/unbind', JSON.stringify({from: 'remote', to: 'Coordinator'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/unbind', stringify({from: 'remote', to: 'Coordinator'})); await flushPromises(); expect(endpoint.unbind).toHaveBeenCalledTimes(3); expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", target); @@ -119,7 +120,7 @@ describe('Bind', () => { expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", target); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/unbind', - JSON.stringify({"data":{"from":"remote","to":"Coordinator","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}), + stringify({"data":{"from":"remote","to":"Coordinator","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -129,7 +130,7 @@ describe('Bind', () => { const target = zigbeeHerdsman.groups.group_1; const endpoint = device.getEndpoint(1); mockClear(device); - MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote', to: 'group_1'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'group_1'})); await flushPromises(); expect(endpoint.bind).toHaveBeenCalledTimes(3); expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target); @@ -137,7 +138,7 @@ describe('Bind', () => { expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', - JSON.stringify({"data":{"from":"remote","to":"group_1","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}), + stringify({"data":{"from":"remote","to":"group_1","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -147,7 +148,7 @@ describe('Bind', () => { const target = zigbeeHerdsman.groups.group_1; const endpoint = device.getEndpoint(1); mockClear(device); - MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote', to: '1'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: '1'})); await flushPromises(); expect(endpoint.bind).toHaveBeenCalledTimes(3); expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target); @@ -155,7 +156,7 @@ describe('Bind', () => { expect(endpoint.bind).toHaveBeenCalledWith("genScenes", target); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', - JSON.stringify({"data":{"from":"remote","to":"1","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}), + stringify({"data":{"from":"remote","to":"1","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -166,12 +167,12 @@ describe('Bind', () => { const endpoint = device.getEndpoint(1); mockClear(device); endpoint.bind.mockImplementation(() => {throw new Error('failed')}); - MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote', to: 'bulb_color'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'bulb_color'})); await flushPromises(); expect(endpoint.bind).toHaveBeenCalledTimes(3); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', - JSON.stringify({"data":{"from":"remote","to":"bulb_color","clusters":[],"failed":["genScenes","genOnOff","genLevelCtrl"]},"status":"error","error":"Failed to bind"}), + stringify({"data":{"from":"remote","to":"bulb_color","clusters":[],"failed":["genScenes","genOnOff","genLevelCtrl"]},"status":"error","error":"Failed to bind"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -181,13 +182,13 @@ describe('Bind', () => { const target = zigbeeHerdsman.devices.QBKG03LM.getEndpoint(3); const endpoint = device.getEndpoint(2); mockClear(device); - MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote/ep2', to: 'wall_switch_double/right'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote/ep2', to: 'wall_switch_double/right'})); await flushPromises(); expect(endpoint.bind).toHaveBeenCalledTimes(1); expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', - JSON.stringify({"data":{"from":"remote/ep2","to":"wall_switch_double/right","clusters":["genOnOff"],"failed":[]},"status":"ok"}), + stringify({"data":{"from":"remote/ep2","to":"wall_switch_double/right","clusters":["genOnOff"],"failed":[]},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -197,13 +198,13 @@ describe('Bind', () => { const target = zigbeeHerdsman.devices.QBKG04LM.getEndpoint(2); const endpoint = device.getEndpoint(2); mockClear(device); - MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote/ep2', to: 'wall_switch'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote/ep2', to: 'wall_switch'})); await flushPromises(); expect(endpoint.bind).toHaveBeenCalledTimes(1); expect(endpoint.bind).toHaveBeenCalledWith("genOnOff", target); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', - JSON.stringify({"data":{"from":"remote/ep2","to":"wall_switch","clusters":["genOnOff"],"failed":[]},"status":"ok"}), + stringify({"data":{"from":"remote/ep2","to":"wall_switch","clusters":["genOnOff"],"failed":[]},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -213,7 +214,7 @@ describe('Bind', () => { const target = 'default_bind_group'; const endpoint = device.getEndpoint(1); mockClear(device); - MQTT.events.message('zigbee2mqtt/bridge/request/device/unbind', JSON.stringify({from: 'remote', to: target})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/unbind', stringify({from: 'remote', to: target})); await flushPromises(); expect(endpoint.unbind).toHaveBeenCalledTimes(3); expect(endpoint.unbind).toHaveBeenCalledWith("genOnOff", 901); @@ -221,7 +222,7 @@ describe('Bind', () => { expect(endpoint.unbind).toHaveBeenCalledWith("genScenes", 901); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/unbind', - JSON.stringify({"data":{"from":"remote","to":"default_bind_group","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}), + stringify({"data":{"from":"remote","to":"default_bind_group","clusters":["genScenes","genOnOff","genLevelCtrl"],"failed":[]},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -231,11 +232,11 @@ describe('Bind', () => { const target = zigbeeHerdsman.devices.bulb_color.getEndpoint(1); const endpoint = device.getEndpoint(1); mockClear(device); - MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote_not_existing', to: 'bulb_color'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote_not_existing', to: 'bulb_color'})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', - JSON.stringify({"data":{"from":"remote_not_existing","to":"bulb_color"},"status":"error","error":"Source device 'remote_not_existing' does not exist"}), + stringify({"data":{"from":"remote_not_existing","to":"bulb_color"},"status":"error","error":"Source device 'remote_not_existing' does not exist"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -245,11 +246,11 @@ describe('Bind', () => { const target = zigbeeHerdsman.devices.bulb_color.getEndpoint(1); const endpoint = device.getEndpoint(1); mockClear(device); - MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', JSON.stringify({from: 'remote', to: 'bulb_color_not_existing'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'bulb_color_not_existing'})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/bind', - JSON.stringify({"data":{"from":"remote","to":"bulb_color_not_existing"},"status":"error","error":"Target device or group 'bulb_color_not_existing' does not exist"}), + stringify({"data":{"from":"remote","to":"bulb_color_not_existing"},"status":"error","error":"Target device or group 'bulb_color_not_existing' does not exist"}), {retain: false, qos: 0}, expect.any(Function) ); }); diff --git a/test/bridge.test.js b/test/bridge.test.js index 5fe54499..1fd9c373 100644 --- a/test/bridge.test.js +++ b/test/bridge.test.js @@ -5,6 +5,7 @@ const MQTT = require('./stub/mqtt'); const settings = require('../lib/util/settings'); const Controller = require('../lib/controller'); const flushPromises = () => new Promise(setImmediate); +const stringify = require('json-stable-stringify'); const {coordinator, bulb, unsupported, WXKG11LM} = zigbeeHerdsman.devices; zigbeeHerdsman.returnDevices.push(coordinator.ieeeAddr); @@ -38,7 +39,7 @@ describe('Bridge', () => { const version = await require('../lib/util/utils').getZigbee2mqttVersion(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/info', - JSON.stringify({"version":version.version,"commit":version.commitHash,"coordinator":{"type":"z-Stack","meta":{"version":1,"revision":20190425}},"network":{"channel":15,"pan_id":5674,"extended_pan_id":[0,11,22]},"log_level":"info","permit_join":false}), + stringify({"version":version.version,"commit":version.commitHash,"coordinator":{"type":"z-Stack","meta":{"version":1,"revision":20190425}},"network":{"channel":15,"pan_id":5674,"extended_pan_id":[0,11,22]},"log_level":"info","permit_join":false}), { retain: true, qos: 0 }, expect.any(Function) ); @@ -47,7 +48,7 @@ describe('Bridge', () => { it('Should publish devices on startup', async () => { expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/devices', - JSON.stringify([{"ieee_address":"0x000b57fffec6a5b2","type":"Router","network_address":40369,"supported":true,"friendly_name":"bulb","definition":{"model":"LED1545G12","vendor":"IKEA","description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","supports":"on/off, brightness, color temperature"},"power_source":"Mains (single phase)","date_code":null,"interviewing":false,"interview_completed":true},{"ieee_address":"0x0017880104e45518","type":"EndDevice","network_address":6536,"supported":false,"friendly_name":"0x0017880104e45518","definition":null,"power_source":"Battery","date_code":null,"interviewing":false,"interview_completed":true},{"ieee_address":"0x0017880104e45520","type":"EndDevice","network_address":6537,"supported":true,"friendly_name":"button","definition":{"model":"WXKG11LM","vendor":"Xiaomi","description":"Aqara wireless switch","supports":"single, double click (and triple, quadruple, hold, release depending on model)"},"power_source":"Battery","date_code":null,"interviewing":false,"interview_completed":true}]), + stringify([{"ieee_address":"0x000b57fffec6a5b2","type":"Router","network_address":40369,"supported":true,"friendly_name":"bulb","definition":{"model":"LED1545G12","vendor":"IKEA","description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","supports":"on/off, brightness, color temperature"},"power_source":"Mains (single phase)","date_code":null,"interviewing":false,"interview_completed":true},{"ieee_address":"0x0017880104e45518","type":"EndDevice","network_address":6536,"supported":false,"friendly_name":"0x0017880104e45518","definition":null,"power_source":"Battery","date_code":null,"interviewing":false,"interview_completed":true},{"ieee_address":"0x0017880104e45520","type":"EndDevice","network_address":6537,"supported":true,"friendly_name":"button","definition":{"model":"WXKG11LM","vendor":"Xiaomi","description":"Aqara wireless switch","supports":"single, double click (and triple, quadruple, hold, release depending on model)"},"power_source":"Battery","date_code":null,"interviewing":false,"interview_completed":true}]), { retain: true, qos: 0 }, expect.any(Function) ); @@ -60,7 +61,7 @@ describe('Bridge', () => { logger.info("this is a test"); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/logging', - JSON.stringify({message: 'this is a test', level: 'info'}), + stringify({message: 'this is a test', level: 'info'}), { retain: false, qos: 0 }, expect.any(Function) ); @@ -83,7 +84,7 @@ describe('Bridge', () => { logger.setTransportsEnabled(true); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/groups', - JSON.stringify([{"id":1,"friendly_name":"group_1","members":[]},{"id":15071,"friendly_name":"group_tradfri_remote","members":[]},{"id":99,"friendly_name":99,"members":[]},{"id":11,"friendly_name":"group_with_tradfri","members":[]},{"id":2,"friendly_name":"group_2","members":[]}]), + stringify([{"id":1,"friendly_name":"group_1","members":[]},{"id":15071,"friendly_name":"group_tradfri_remote","members":[]},{"id":99,"friendly_name":99,"members":[]},{"id":11,"friendly_name":"group_with_tradfri","members":[]},{"id":2,"friendly_name":"group_2","members":[]}]), { retain: true, qos: 0 }, expect.any(Function) ); @@ -96,7 +97,7 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', - JSON.stringify({"type":"device_joined","data":{"friendly_name":"bulb","ieee_address":"0x000b57fffec6a5b2"}}), + stringify({"type":"device_joined","data":{"friendly_name":"bulb","ieee_address":"0x000b57fffec6a5b2"}}), { retain: false, qos: 0 }, expect.any(Function) ); @@ -109,7 +110,7 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', - JSON.stringify({"type":"device_interview","data":{"friendly_name":"bulb","status":"started","ieee_address":"0x000b57fffec6a5b2"}}), + stringify({"type":"device_interview","data":{"friendly_name":"bulb","status":"started","ieee_address":"0x000b57fffec6a5b2"}}), { retain: false, qos: 0 }, expect.any(Function) ); @@ -122,7 +123,7 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledTimes(2); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', - JSON.stringify({"type":"device_interview","data":{"friendly_name":"bulb","status":"failed","ieee_address":"0x000b57fffec6a5b2"}}), + stringify({"type":"device_interview","data":{"friendly_name":"bulb","status":"failed","ieee_address":"0x000b57fffec6a5b2"}}), { retain: false, qos: 0 }, expect.any(Function) ); @@ -142,13 +143,13 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledTimes(4); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', - JSON.stringify({"type":"device_interview","data":{"friendly_name":"bulb","status":"successful","ieee_address":"0x000b57fffec6a5b2","supported":true,"definition":{"model":"LED1545G12","vendor":"IKEA","description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","supports":"on/off, brightness, color temperature"}}}), + stringify({"type":"device_interview","data":{"friendly_name":"bulb","status":"successful","ieee_address":"0x000b57fffec6a5b2","supported":true,"definition":{"model":"LED1545G12","vendor":"IKEA","description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","supports":"on/off, brightness, color temperature"}}}), { retain: false, qos: 0 }, expect.any(Function) ); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', - JSON.stringify({"type":"device_interview","data":{"friendly_name":"0x0017880104e45518","status":"successful","ieee_address":"0x0017880104e45518","supported":false,"definition":null}}), + stringify({"type":"device_interview","data":{"friendly_name":"0x0017880104e45518","status":"successful","ieee_address":"0x0017880104e45518","supported":false,"definition":null}}), { retain: false, qos: 0 }, expect.any(Function) ); @@ -167,7 +168,7 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledTimes(2); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/event', - JSON.stringify({"type":"device_leave","data":{"ieee_address":"0x000b57fffec6a5b2"}}), + stringify({"type":"device_leave","data":{"ieee_address":"0x000b57fffec6a5b2"}}), { retain: false, qos: 0 }, expect.any(Function) ); @@ -189,31 +190,31 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), { retain: true, qos: 0 }, expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', - JSON.stringify({"data":{"value":true},"status":"ok"}), + stringify({"data":{"value":true},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); zigbeeHerdsman.permitJoin.mockClear(); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', JSON.stringify({"value": false})); + MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', stringify({"value": false})); await flushPromises(); expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledTimes(1); expect(zigbeeHerdsman.permitJoin).toHaveBeenCalledWith(false); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), { retain: true, qos: 0 }, expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', - JSON.stringify({"data":{"value":false},"status":"ok"}), + stringify({"data":{"value":false},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); it('Should put transaction in response when request is done with transaction', async () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', JSON.stringify({"value": false, "transaction": 22})); + MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', stringify({"value": false, "transaction": 22})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', - JSON.stringify({"data":{"value":false},"status":"ok", "transaction": 22}), + stringify({"data":{"value":false},"status":"ok", "transaction": 22}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -221,29 +222,29 @@ describe('Bridge', () => { it('Should put error in response when request fails', async () => { zigbeeHerdsman.permitJoin.mockImplementationOnce(() => {throw new Error('Failed to connect to adapter')}); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', JSON.stringify({"value": false})); + MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', stringify({"value": false})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', - JSON.stringify({"data":{},"status":"error","error": "Failed to connect to adapter"}), + 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 () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', JSON.stringify({"value_not_good": false})); + MQTT.events.message('zigbee2mqtt/bridge/request/permit_join', stringify({"value_not_good": false})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/permit_join', - JSON.stringify({"data":{},"status":"error","error": "No value given"}), + stringify({"data":{},"status":"error","error": "No value given"}), {retain: false, qos: 0}, expect.any(Function) ); }); it('Coverage satisfaction', async () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/random', JSON.stringify({"value": false})); + MQTT.events.message('zigbee2mqtt/bridge/request/random', stringify({"value": false})); const device = zigbeeHerdsman.devices.bulb; await zigbeeHerdsman.events.message({data: {onOff: 1}, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10}); await flushPromises(); @@ -255,7 +256,7 @@ describe('Bridge', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/health_check', - JSON.stringify({"data":{"healthy": true},"status":"ok"}), + stringify({"data":{"healthy": true},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -273,7 +274,7 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/remove', - JSON.stringify({"data":{"id": "bulb","block":false,"force":false},"status":"ok"}), + stringify({"data":{"id": "bulb","block":false,"force":false},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); expect(settings.get().blocklist).toStrictEqual([]); @@ -282,7 +283,7 @@ describe('Bridge', () => { it('Should allow to remove device by object ID', async () => { const device = zigbeeHerdsman.devices.bulb; MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', JSON.stringify({id: "bulb"})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', stringify({id: "bulb"})); await flushPromises(); expect(device.removeFromNetwork).toHaveBeenCalledTimes(1); expect(device.removeFromDatabase).not.toHaveBeenCalled(); @@ -290,7 +291,7 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/remove', - JSON.stringify({"data":{"id": "bulb","block":false,"force":false},"status":"ok"}), + stringify({"data":{"id": "bulb","block":false,"force":false},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -298,7 +299,7 @@ describe('Bridge', () => { it('Should allow to force remove device', async () => { const device = zigbeeHerdsman.devices.bulb; MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', JSON.stringify({id: "bulb", force: true})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', stringify({id: "bulb", force: true})); await flushPromises(); expect(device.removeFromDatabase).toHaveBeenCalledTimes(1); expect(device.removeFromNetwork).not.toHaveBeenCalled(); @@ -306,7 +307,7 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/remove', - JSON.stringify({"data":{"id": "bulb","block":false,"force":true},"status":"ok"}), + stringify({"data":{"id": "bulb","block":false,"force":true},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -314,14 +315,14 @@ describe('Bridge', () => { it('Should allow to block device', async () => { const device = zigbeeHerdsman.devices.bulb; MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', JSON.stringify({id: "bulb", block: true, force: true})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', stringify({id: "bulb", block: true, force: true})); await flushPromises(); expect(device.removeFromDatabase).toHaveBeenCalledTimes(1); expect(settings.getDevice('bulb')).toBeNull(); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/remove', - JSON.stringify({"data":{"id": "bulb","block":true,"force":true},"status":"ok"}), + stringify({"data":{"id": "bulb","block":true,"force":true},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); expect(settings.get().blocklist).toStrictEqual(["0x000b57fffec6a5b2"]); @@ -337,7 +338,7 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/remove', - JSON.stringify({"data":{"id": "group_1", "force": false},"status":"ok"}), + stringify({"data":{"id": "group_1", "force": false},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -345,14 +346,14 @@ describe('Bridge', () => { it('Should allow to force remove group', async () => { const group = zigbeeHerdsman.groups.group_1; MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/remove', JSON.stringify({id: "group_1", force: true})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/remove', stringify({id: "group_1", force: true})); await flushPromises(); expect(group.removeFromDatabase).toHaveBeenCalledTimes(1); expect(settings.getGroup('group_1')).toBeNull(); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/remove', - JSON.stringify({"data":{"id": "group_1", "force": true},"status":"ok"}), + stringify({"data":{"id": "group_1", "force": true},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -360,11 +361,11 @@ describe('Bridge', () => { it('Should throw error on removing non-existing device', async () => { const device = zigbeeHerdsman.devices.bulb; MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', JSON.stringify({id: "non-existing-device"})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', stringify({id: "non-existing-device"})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/remove', - JSON.stringify({"data":{},"status":"error","error":"Device 'non-existing-device' does not exist"}), + stringify({"data":{},"status":"error","error":"Device 'non-existing-device' does not exist"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -373,61 +374,61 @@ describe('Bridge', () => { const device = zigbeeHerdsman.devices.bulb; MQTT.publish.mockClear(); device.removeFromNetwork.mockImplementationOnce(() => {throw new Error('device timeout')}) - MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', JSON.stringify({id: "bulb"})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', stringify({id: "bulb"})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/remove', - JSON.stringify({"data":{},"status":"error","error":"Failed to remove device 'bulb' (block: false, force: false) (Error: device timeout)"}), + 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 () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', JSON.stringify({from: 'bulb', to: 'bulb_new_name'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', stringify({from: 'bulb', to: 'bulb_new_name'})); await flushPromises(); expect(settings.getDevice('bulb')).toBeNull(); expect(settings.getDevice('bulb_new_name')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendly_name": "bulb_new_name", "friendlyName": "bulb_new_name", "retain": true}); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/rename', - JSON.stringify({"data":{"from":"bulb","to":"bulb_new_name"},"status":"ok"}), + stringify({"data":{"from":"bulb","to":"bulb_new_name"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); it('Should allow rename group', async () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/rename', JSON.stringify({from: 'group_1', to: 'group_new_name'})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/rename', stringify({from: 'group_1', to: 'group_new_name'})); await flushPromises(); expect(settings.getGroup('group_1')).toBeNull(); expect(settings.getGroup('group_new_name')).toStrictEqual({"ID": 1, "devices": [], "friendly_name": "group_new_name", "friendlyName": "group_new_name", "optimistic": true, "retain": false}); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/rename', - JSON.stringify({"data":{"from":"group_1","to":"group_new_name"},"status":"ok"}), + stringify({"data":{"from":"group_1","to":"group_new_name"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); it('Should throw error on invalid device rename payload', async () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', JSON.stringify({from_bla: 'bulb', to: 'bulb_new_name'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', stringify({from_bla: 'bulb', to: 'bulb_new_name'})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/rename', - JSON.stringify({"data":{},"status":"error","error":"Invalid payload"}), + stringify({"data":{},"status":"error","error":"Invalid payload"}), {retain: false, qos: 0}, expect.any(Function) ); }); it('Should throw error on non-existing device rename', async () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', JSON.stringify({from: 'bulb_not_existing', to: 'bulb_new_name'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', stringify({from: 'bulb_not_existing', to: 'bulb_new_name'})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/rename', - JSON.stringify({"data":{},"status":"error","error":"Device 'bulb_not_existing' does not exist"}), + stringify({"data":{},"status":"error","error":"Device 'bulb_not_existing' does not exist"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -435,25 +436,25 @@ describe('Bridge', () => { it('Should allow to rename last joined device', async () => { MQTT.publish.mockClear(); await zigbeeHerdsman.events.deviceJoined({device: zigbeeHerdsman.devices.bulb}); - MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', JSON.stringify({last: true, to: 'bulb_new_name'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', stringify({last: true, to: 'bulb_new_name'})); await flushPromises(); expect(settings.getDevice('bulb')).toBeNull(); expect(settings.getDevice('bulb_new_name')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendly_name": "bulb_new_name", "friendlyName": "bulb_new_name", "retain": true}); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/rename', - JSON.stringify({"data":{"from":"bulb","to":"bulb_new_name"},"status":"ok"}), + stringify({"data":{"from":"bulb","to":"bulb_new_name"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); it('Should throw error when renaming last joined device but none has joined', async () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', JSON.stringify({last: true, to: 'bulb_new_name'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', stringify({last: true, to: 'bulb_new_name'})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/rename', - JSON.stringify({"data":{},"status":"error","error":"No device has joined since start"}), + stringify({"data":{},"status":"error","error":"No device has joined since start"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -461,12 +462,12 @@ describe('Bridge', () => { it('Should allow change device options', async () => { MQTT.publish.mockClear(); expect(settings.getDevice('bulb')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendly_name": "bulb", "friendlyName": "bulb", "retain": true}); - MQTT.events.message('zigbee2mqtt/bridge/request/device/options', JSON.stringify({options: {retain: false, transition: 1}, id: 'bulb'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/options', stringify({options: {retain: false, transition: 1}, id: 'bulb'})); await flushPromises(); expect(settings.getDevice('bulb')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendly_name": "bulb", "friendlyName": "bulb", "retain": false, "transition": 1}); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/options', - JSON.stringify({"data":{"from":{"retain": true},"to":{"retain": false,"transition":1}, "id":"bulb"},"status":"ok"}), + stringify({"data":{"from":{"retain": true},"to":{"retain": false,"transition":1}, "id":"bulb"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -474,23 +475,23 @@ describe('Bridge', () => { it('Should allow change group options', async () => { MQTT.publish.mockClear(); expect(settings.getGroup('group_1')).toStrictEqual({"ID": 1, "devices": [], "friendly_name": "group_1", "retain": false, "friendlyName": "group_1", "optimistic": true}); - MQTT.events.message('zigbee2mqtt/bridge/request/group/options', JSON.stringify({options: {retain: true, transition: 1}, id: 'group_1'})); + MQTT.events.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, "devices": [], "friendly_name": "group_1", "retain": true, "friendlyName": "group_1", "optimistic": true, "transition": 1}); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/options', - JSON.stringify({"data":{"from":{"optimistic": true,"retain": false},"to":{"optimistic": true,"retain": true,"transition":1}, "id":"group_1"},"status":"ok"}), + stringify({"data":{"from":{"optimistic": true,"retain": false},"to":{"optimistic": true,"retain": true,"transition":1}, "id":"group_1"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); it('Should throw error on invalid device change options payload', async () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/device/options', JSON.stringify({options_: {retain: true, transition: 1}, id: 'bulb'})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/options', stringify({options_: {retain: true, transition: 1}, id: 'bulb'})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/options', - JSON.stringify({"data":{},"status":"error","error":"Invalid payload"}), + stringify({"data":{},"status":"error","error":"Invalid payload"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -503,31 +504,31 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/add', - JSON.stringify({"data":{"friendly_name":"group_193","id": 3},"status":"ok"}), + 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 () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/add', JSON.stringify({friendly_name: "group_193", id: 9})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/add', stringify({friendly_name: "group_193", id: 9})); await flushPromises(); expect(settings.getGroup('group_193')).toStrictEqual({"ID": 9, "devices": [], "friendly_name": "group_193", "friendlyName": "group_193", "optimistic": true}); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/add', - JSON.stringify({"data":{"friendly_name":"group_193","id": 9},"status":"ok"}), + stringify({"data":{"friendly_name":"group_193","id": 9},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); it('Should throw error when add with invalid payload', async () => { MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/add', JSON.stringify({friendly_name9: "group_193"})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/add', stringify({friendly_name9: "group_193"})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/add', - JSON.stringify({"data":{},"status":"error","error":"Invalid payload"}), + stringify({"data":{},"status":"error","error":"Invalid payload"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -542,21 +543,21 @@ describe('Bridge', () => { expect(MQTT.publish).not.toHaveBeenCalledWith('zigbee2mqtt/button/action', 'single', {retain: false, qos: 0}, expect.any(Function)); // Disable when already disabled should go OK - MQTT.events.message('zigbee2mqtt/bridge/request/config/homeassistant', JSON.stringify({value: false})); + MQTT.events.message('zigbee2mqtt/bridge/request/config/homeassistant', stringify({value: false})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/config/homeassistant', - JSON.stringify({"data":{"value":false},"status":"ok"}), + stringify({"data":{"value":false},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); expect(settings.get().homeassistant).toBeFalsy(); // Enable - MQTT.events.message('zigbee2mqtt/bridge/request/config/homeassistant', JSON.stringify({value: true})); + MQTT.events.message('zigbee2mqtt/bridge/request/config/homeassistant', stringify({value: true})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/config/homeassistant', - JSON.stringify({"data":{"value":true},"status":"ok"}), + stringify({"data":{"value":true},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); expect(settings.get().homeassistant).toBeTruthy(); @@ -566,11 +567,11 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/button/action', 'single', {retain: false, qos: 0}, expect.any(Function)); // Disable - MQTT.events.message('zigbee2mqtt/bridge/request/config/homeassistant', JSON.stringify({value: false})); + MQTT.events.message('zigbee2mqtt/bridge/request/config/homeassistant', stringify({value: false})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/config/homeassistant', - JSON.stringify({"data":{"value":false},"status":"ok"}), + stringify({"data":{"value":false},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); expect(settings.get().homeassistant).toBeFalsy(); @@ -587,7 +588,7 @@ describe('Bridge', () => { expect(settings.get().homeassistant).toBeFalsy(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/config/homeassistant', - JSON.stringify({"data":{},"status":"error","error":"'invalid_one' is not an allowed value, allowed: true,false"}), + stringify({"data":{},"status":"error","error":"'invalid_one' is not an allowed value, allowed: true,false"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -599,7 +600,7 @@ describe('Bridge', () => { expect(settings.get().advanced.last_seen).toBe('ISO_8601'); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/config/last_seen', - JSON.stringify({"data":{"value":"ISO_8601"},"status":"ok"}), + stringify({"data":{"value":"ISO_8601"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -611,7 +612,7 @@ describe('Bridge', () => { expect(settings.get().advanced.last_seen).toBe('disable'); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/config/last_seen', - JSON.stringify({"data":{},"status":"error","error":"'invalid_one' is not an allowed value, allowed: disable,ISO_8601,epoch,ISO_8601_local"}), + stringify({"data":{},"status":"error","error":"'invalid_one' is not an allowed value, allowed: disable,ISO_8601,epoch,ISO_8601_local"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -623,7 +624,7 @@ describe('Bridge', () => { expect(settings.get().advanced.elapsed).toBe(true); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/config/elapsed', - JSON.stringify({"data":{"value":true},"status":"ok"}), + stringify({"data":{"value":true},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -635,7 +636,7 @@ describe('Bridge', () => { expect(settings.get().advanced.elapsed).toBe(false); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/config/elapsed', - JSON.stringify({"data":{},"status":"error","error":"'not_valid' is not an allowed value, allowed: true,false"}), + stringify({"data":{},"status":"error","error":"'not_valid' is not an allowed value, allowed: true,false"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -648,7 +649,7 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/info', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/config/log_level', - JSON.stringify({"data":{"value":'debug'},"status":"ok"}), + stringify({"data":{"value":'debug'},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -659,7 +660,7 @@ describe('Bridge', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/config/log_level', - JSON.stringify({"data":{},"status":"error","error":"'not_valid' is not an allowed value, allowed: error,warn,info,debug"}), + stringify({"data":{},"status":"error","error":"'not_valid' is not an allowed value, allowed: error,warn,info,debug"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -673,7 +674,7 @@ describe('Bridge', () => { expect(zigbeeHerdsman.touchlinkFactoryReset).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/touchlink/factory_reset', - JSON.stringify({"data":{},"status":"ok"}), + stringify({"data":{},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -687,7 +688,7 @@ describe('Bridge', () => { expect(zigbeeHerdsman.touchlinkFactoryReset).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/touchlink/factory_reset', - JSON.stringify({"data":{},"status":"error","error":"Failed to factory reset device through Touchlink"}), + stringify({"data":{},"status":"error","error":"Failed to factory reset device through Touchlink"}), {retain: false, qos: 0}, expect.any(Function) ); }); diff --git a/test/configure.test.js b/test/configure.test.js index 205fcfa5..5e39f409 100644 --- a/test/configure.test.js +++ b/test/configure.test.js @@ -6,6 +6,7 @@ const settings = require('../lib/util/settings'); const Controller = require('../lib/controller'); const flushPromises = () => new Promise(setImmediate); const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); +const stringify = require('json-stable-stringify'); const mocksClear = [MQTT.publish, logger.warn, logger.debug]; @@ -109,38 +110,38 @@ describe('Configure', () => { expectRemoteConfigured(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/configure', - JSON.stringify({"data":{"id": "remote"},"status":"ok"}), + 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 MQTT.events.message('zigbee2mqtt/bridge/request/device/configure', JSON.stringify({id: "not_existing_device"})); + await MQTT.events.message('zigbee2mqtt/bridge/request/device/configure', stringify({id: "not_existing_device"})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/configure', - JSON.stringify({"data":{"id": "not_existing_device"},"status":"error","error": "Device 'not_existing_device' does not exist"}), + stringify({"data":{"id": "not_existing_device"},"status":"error","error": "Device 'not_existing_device' does not exist"}), {retain: false, qos: 0}, expect.any(Function) ); }); it('Fail to configure via MQTT when configure fails', async () => { zigbeeHerdsman.devices.remote.getEndpoint(1).bind.mockImplementationOnce(async () => {throw new Error('Bind timeout after 10s')}); - await MQTT.events.message('zigbee2mqtt/bridge/request/device/configure', JSON.stringify({id: "remote"})); + await MQTT.events.message('zigbee2mqtt/bridge/request/device/configure', stringify({id: "remote"})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/configure', - JSON.stringify({"data":{"id": "remote"},"status":"error","error": "Failed to configure (Bind timeout after 10s)"}), + 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 MQTT.events.message('zigbee2mqtt/bridge/request/device/configure', JSON.stringify({id: "bulb", transaction: 20})); + await MQTT.events.message('zigbee2mqtt/bridge/request/device/configure', stringify({id: "bulb", transaction: 20})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/configure', - JSON.stringify({"data":{"id": "bulb"},"status":"error","error": "Device 'bulb' cannot be configured","transaction":20}), + stringify({"data":{"id": "bulb"},"status":"error","error": "Device 'bulb' cannot be configured","transaction":20}), {retain: false, qos: 0}, expect.any(Function) ); }); diff --git a/test/controller.test.js b/test/controller.test.js index 0c7c7876..e766e9ae 100644 --- a/test/controller.test.js +++ b/test/controller.test.js @@ -6,6 +6,7 @@ const path = require('path'); const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {}); const settings = require('../lib/util/settings'); const Controller = require('../lib/controller'); +const stringify = require('json-stable-stringify'); const flushPromises = () => new Promise(setImmediate); const tmp = require('tmp'); const mocksClear = [ @@ -42,8 +43,8 @@ describe('Controller', () => { expect(logger.info).toHaveBeenCalledWith('0x0017880104e45518 (0x0017880104e45518): Not supported (EndDevice)'); expect(MQTT.connect).toHaveBeenCalledTimes(1); expect(MQTT.connect).toHaveBeenCalledWith("mqtt://localhost", {"will": {"payload": "offline", "retain": true, "topic": "zigbee2mqtt/bridge/state"}}); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', '{"state":"ON","brightness":50,"color_temp":370,"linkquality":99}',{ retain: true, qos: 0 }, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/remote', '{"brightness":255}', { retain: true, qos: 0 }, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99}),{ retain: true, qos: 0 }, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/remote', stringify({"brightness":255}), { retain: true, qos: 0 }, expect.any(Function)); }); it('Start controller when permit join fails', async () => { @@ -104,8 +105,8 @@ describe('Controller', () => { data.writeDefaultState(); await controller.start(); await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", `{"state":"ON","brightness":50,"color_temp":370,"linkquality":99}`, {"qos": 0, "retain": true}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/remote", `{"brightness":255}`, {"qos": 0, "retain": true}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99}), {"qos": 0, "retain": true}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/remote", stringify({"brightness":255}), {"qos": 0, "retain": true}, expect.any(Function)); }); it('Start controller should not publish cached states when disabled', async () => { @@ -148,7 +149,7 @@ describe('Controller', () => { await flushPromises(); expect(logger.error).toHaveBeenCalledTimes(2); expect(logger.error).toHaveBeenCalledWith("Not connected to MQTT server!"); - expect(logger.error).toHaveBeenCalledWith("Cannot send message: topic: 'zigbee2mqtt/bulb', payload: '{\"state\":\"ON\",\"brightness\":50,\"color_temp\":370,\"linkquality\":99,\"color\":{\"r\":100,\"g\":50,\"b\":10},\"dummy\":{\"1\":\"yes\",\"2\":\"no\"}}"); + expect(logger.error).toHaveBeenCalledWith("Cannot send message: topic: 'zigbee2mqtt/bulb', payload: '{\"brightness\":50,\"color\":{\"b\":10,\"g\":50,\"r\":100},\"color_temp\":370,\"dummy\":{\"1\":\"yes\",\"2\":\"no\"},\"linkquality\":99,\"state\":\"ON\"}"); controller.mqtt.client.reconnecting = false; }); @@ -296,7 +297,7 @@ describe('Controller', () => { const payload = {device}; await zigbeeHerdsman.events.deviceJoined(payload); await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", '{"type":"device_connected","message":{"friendly_name":"bulb"}}', {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", stringify({"type":"device_connected","message":{"friendly_name":"bulb"}}), {"retain": false, qos: 0}, expect.any(Function)); }); it('acceptJoiningDeviceHandler reject device on blocklist', async () => { @@ -354,7 +355,7 @@ describe('Controller', () => { zigbeeHerdsman.events.deviceJoined(payload); zigbeeHerdsman.events.deviceJoined(payload); await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", '{"type":"device_connected","message":{"friendly_name":"bulb"}}', {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", stringify({"type":"device_connected","message":{"friendly_name":"bulb"}}), {"retain": false, qos: 0}, expect.any(Function)); }); it('On zigbee deviceInterview started', async () => { @@ -363,7 +364,7 @@ describe('Controller', () => { const payload = {device, status: 'started'}; await zigbeeHerdsman.events.deviceInterview(payload); await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', '{"type":"pairing","message":"interview_started","meta":{"friendly_name":"bulb"}}', { retain: false, qos: 0 }, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', stringify({"type":"pairing","message":"interview_started","meta":{"friendly_name":"bulb"}}), { retain: false, qos: 0 }, expect.any(Function)); }); it('On zigbee deviceInterview failed', async () => { @@ -372,7 +373,7 @@ describe('Controller', () => { const payload = {device, status: 'failed'}; await zigbeeHerdsman.events.deviceInterview(payload); await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', '{"type":"pairing","message":"interview_failed","meta":{"friendly_name":"bulb"}}', { retain: false, qos: 0 }, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', stringify({"type":"pairing","message":"interview_failed","meta":{"friendly_name":"bulb"}}), { retain: false, qos: 0 }, expect.any(Function)); }); it('On zigbee deviceInterview successful supported', async () => { @@ -381,7 +382,7 @@ describe('Controller', () => { const payload = {device, status: 'successful'}; await zigbeeHerdsman.events.deviceInterview(payload); await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', '{"type":"pairing","message":"interview_successful","meta":{"friendly_name":"bulb","model":"LED1545G12","vendor":"IKEA","description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","supported":true}}', { retain: false, qos: 0 }, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', stringify({"type":"pairing","message":"interview_successful","meta":{"friendly_name":"bulb","model":"LED1545G12","vendor":"IKEA","description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","supported":true}}), { retain: false, qos: 0 }, expect.any(Function)); }); it('On zigbee deviceInterview successful not supported', async () => { @@ -390,7 +391,7 @@ describe('Controller', () => { const payload = {device, status: 'successful'}; await zigbeeHerdsman.events.deviceInterview(payload); await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', '{"type":"pairing","message":"interview_successful","meta":{"friendly_name":"0x0017880104e45518","supported":false}}', { retain: false, qos: 0 }, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', stringify({"type":"pairing","message":"interview_successful","meta":{"friendly_name":"0x0017880104e45518","supported":false}}), { retain: false, qos: 0 }, expect.any(Function)); }); it('On zigbee event device announce', async () => { @@ -400,7 +401,7 @@ describe('Controller', () => { await zigbeeHerdsman.events.deviceAnnounce(payload); await flushPromises(); expect(logger.debug).toHaveBeenCalledWith(`Device 'bulb' announced itself`); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', '{"type":"device_announced","message":"announce","meta":{"friendly_name":"bulb"}}', { retain: false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', stringify({"type":"device_announced","message":"announce","meta":{"friendly_name":"bulb"}}), { retain: false, qos: 0}, expect.any(Function)); }); it('On zigbee event device leave (removed from database and settings)', async () => { @@ -412,7 +413,7 @@ describe('Controller', () => { const payload = {ieeeAddr: device.ieeeAddr}; await zigbeeHerdsman.events.deviceLeave(payload); await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', '{"type":"device_removed","message":"left_network","meta":{"friendly_name":"0x000b57fffec6a5b2"}}', { retain: false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', stringify({"type":"device_removed","message":"left_network","meta":{"friendly_name":"0x000b57fffec6a5b2"}}), { retain: false, qos: 0}, expect.any(Function)); }); it('On zigbee event device leave (removed from database and NOT settings)', async () => { @@ -423,7 +424,7 @@ describe('Controller', () => { const payload = {ieeeAddr: device.ieeeAddr}; await zigbeeHerdsman.events.deviceLeave(payload); await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', '{"type":"device_removed","message":"left_network","meta":{"friendly_name":"0x000b57fffec6a5b2"}}', { retain: false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/log', stringify({"type":"device_removed","message":"left_network","meta":{"friendly_name":"0x000b57fffec6a5b2"}}), { retain: false, qos: 0}, expect.any(Function)); }); it('Publish entity state attribute output', async () => { @@ -436,8 +437,8 @@ describe('Controller', () => { expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/brightness", "50", {"qos": 0, "retain": true}, expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/color_temp", "370", {"qos": 0, "retain": true}, expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/color", '100,50,10', {"qos": 0, "retain": true}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/dummy-1", 'yes', {"qos": 0, "retain": true}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/dummy-2", 'no', {"qos": 0, "retain": true}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/dummy-1", "yes", {"qos": 0, "retain": true}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/dummy-2", "no", {"qos": 0, "retain": true}, expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/test1", '', {"qos": 0, "retain": true}, expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/test", '', {"qos": 0, "retain": true}, expect.any(Function)); }); @@ -453,7 +454,7 @@ describe('Controller', () => { expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/brightness", "200", {"qos": 0, "retain": true}, expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/color_temp", "370", {"qos": 0, "retain": true}, expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/linkquality", "99", {"qos": 0, "retain": true}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", '{"state":"ON","brightness":200,"color_temp":370,"linkquality":99}', {"qos": 0, "retain": true}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({"state":"ON","brightness":200,"color_temp":370,"linkquality":99}), {"qos": 0, "retain": true}, expect.any(Function)); }); @@ -467,7 +468,7 @@ describe('Controller', () => { expect(MQTT.publish).toHaveBeenCalledTimes(3); expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/state", "ON", {"qos": 0, "retain": true}, expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/brightness", "200", {"qos": 0, "retain": true}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", '{"state":"ON","brightness":200}', {"qos": 0, "retain": true}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({"state":"ON","brightness":200}), {"qos": 0, "retain": true}, expect.any(Function)); }); it('Publish entity state attribute_json output filtered (device_options)', async () => { @@ -480,7 +481,7 @@ describe('Controller', () => { expect(MQTT.publish).toHaveBeenCalledTimes(3); expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/state", "ON", {"qos": 0, "retain": true}, expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb/brightness", "200", {"qos": 0, "retain": true}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", '{"state":"ON","brightness":200}', {"qos": 0, "retain": true}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({"state":"ON","brightness":200}), {"qos": 0, "retain": true}, expect.any(Function)); }); it('Publish entity state with device information', async () => { @@ -489,12 +490,12 @@ describe('Controller', () => { MQTT.publish.mockClear(); await controller.publishEntityState('bulb', {state: 'ON'}); await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', '{"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"device":{"friendlyName":"bulb","model":"LED1545G12","ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369,"type":"Router","manufacturerID":4476,"powerSource":"Mains (single phase)","dateCode":null}}', {"qos": 0, "retain": true}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"device":{"friendlyName":"bulb","model":"LED1545G12","ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369,"type":"Router","manufacturerID":4476,"powerSource":"Mains (single phase)","dateCode":null}}), {"qos": 0, "retain": true}, expect.any(Function)); // Unsupported device should have model "unknown" await controller.publishEntityState('unsupported2', {state: 'ON'}); await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/unsupported2', '{"state":"ON","device":{"friendlyName":"unsupported2","model":"unknown","ieeeAddr":"0x0017880104e45529","networkAddress":6536,"type":"EndDevice","manufacturerID":0,"powerSource":"Battery","dateCode":null}}', {"qos": 0, "retain": false}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/unsupported2', stringify({"state":"ON","device":{"friendlyName":"unsupported2","model":"unknown","ieeeAddr":"0x0017880104e45529","networkAddress":6536,"type":"EndDevice","manufacturerID":0,"powerSource":"Battery","dateCode":null}}), {"qos": 0, "retain": false}, expect.any(Function)); }); it('Should publish entity state without retain', async () => { @@ -503,7 +504,7 @@ describe('Controller', () => { MQTT.publish.mockClear(); await controller.publishEntityState('bulb', {state: 'ON'}); await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', '{"state":"ON","brightness":50,"color_temp":370,"linkquality":99}', {"qos": 0, "retain": false}, expect.any(Function)); + expect(MQTT.publish).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 () => { @@ -512,7 +513,7 @@ describe('Controller', () => { MQTT.publish.mockClear(); await controller.publishEntityState('bulb', {state: 'ON'}); await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', '{"state":"ON","brightness":50,"color_temp":370,"linkquality":99}', {"qos": 0, "retain": true}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99}), {"qos": 0, "retain": true}, expect.any(Function)); }); it('Should publish entity state with expiring retention', async () => { @@ -523,7 +524,7 @@ describe('Controller', () => { MQTT.publish.mockClear(); await controller.publishEntityState('bulb', {state: 'ON'}); await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb', '{"state":"ON","brightness":50,"color_temp":370,"linkquality":99}', {"qos": 0, "retain": true, "properties": {messageExpiryInterval: 37}}, expect.any(Function)); + expect(MQTT.publish).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 () => { @@ -544,8 +545,8 @@ describe('Controller', () => { await controller.publishEntityState('bulb', {brightness: 200}); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(2); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", JSON.stringify({state: "ON"}), {"qos": 0, "retain": true}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", JSON.stringify({state: "ON", brightness: 200}), {"qos": 0, "retain": true}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({state: "ON"}), {"qos": 0, "retain": true}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({state: "ON", brightness: 200}), {"qos": 0, "retain": true}, expect.any(Function)); await controller.stop(); expect(data.stateExists()).toBeFalsy(); }); @@ -559,8 +560,8 @@ describe('Controller', () => { await controller.publishEntityState('bulb', {brightness: 200}); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(2); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", "{\"state\":\"ON\"}", {"qos": 0, "retain": true}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", "{\"brightness\":200}", {"qos": 0, "retain": true}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({"state":"ON"}), {"qos": 0, "retain": true}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({"brightness":200}), {"qos": 0, "retain": true}, expect.any(Function)); }); it('Publish should not do anything for unknown entity', async () => { diff --git a/test/group.test.js b/test/group.test.js index 3660eb23..6c80978a 100644 --- a/test/group.test.js +++ b/test/group.test.js @@ -1,6 +1,7 @@ const data = require('./stub/data'); const logger = require('./stub/logger'); const zigbeeHerdsman = require('./stub/zigbeeHerdsman'); +const stringify = require('json-stable-stringify'); const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters'); zigbeeHerdsman.returnDevices.push('0x00124b00120144ae'); zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b3'); @@ -107,7 +108,7 @@ describe('Groups', () => { await flushPromises(); expect(group.members).toStrictEqual([endpoint]); expect(settings.getGroup('group_1').devices).toStrictEqual([`${device.ieeeAddr}/1`]); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", '{"type":"device_group_add","message":{"friendly_name":"bulb_color","group":"group_1"}}', {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", stringify({"type":"device_group_add","message":{"friendly_name":"bulb_color","group":"group_1"}}), {"retain": false, qos: 0}, expect.any(Function)); }); it('Legacy api: Add to group with slashes via MQTT', async () => { @@ -122,7 +123,7 @@ describe('Groups', () => { await flushPromises(); expect(group.members).toStrictEqual([endpoint]); expect(settings.getGroup('group/with/slashes').devices).toStrictEqual([`${device.ieeeAddr}/1`]); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", '{"type":"device_group_add","message":{"friendly_name":"bulb_color","group":"group/with/slashes"}}', {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", stringify({"type":"device_group_add","message":{"friendly_name":"bulb_color","group":"group/with/slashes"}}), {"retain": false, qos: 0}, expect.any(Function)); // Test if subscribed to topics with slashes expect(MQTT.subscribe).toHaveBeenCalledWith('zigbee2mqtt/bridge/group/+/remove'); @@ -171,7 +172,7 @@ describe('Groups', () => { await flushPromises(); expect(group.members).toStrictEqual([]); expect(settings.getGroup('group_1').devices).toStrictEqual([]); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", '{"type":"device_group_remove","message":{"friendly_name":"bulb_color","group":"group_1"}}', {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", stringify({"type":"device_group_remove","message":{"friendly_name":"bulb_color","group":"group_1"}}), {"retain": false, qos: 0}, expect.any(Function)); }); it('Legacy api: Remove from group via MQTT when in zigbee but not in settings', async () => { @@ -242,7 +243,7 @@ describe('Groups', () => { await flushPromises(); expect(group.members).toStrictEqual([]); expect(settings.getGroup('group_1').devices).toStrictEqual([]); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", '{"type":"device_group_remove_all","message":{"friendly_name":"wall_switch_double"}}', {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", stringify({"type":"device_group_remove_all","message":{"friendly_name":"wall_switch_double"}}), {"retain": false, qos: 0}, expect.any(Function)); }); it('Remove from group all deprecated', async () => { @@ -292,8 +293,8 @@ describe('Groups', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(2); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"ON","linkquality":10}', {"retain": false, qos: 0}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", '{"state":"ON"}', {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"ON","linkquality":10}), {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function)); }); it('Should publish state change of all members when a group changes its state', async () => { @@ -306,11 +307,11 @@ describe('Groups', () => { await flushPromises(); MQTT.publish.mockClear(); - await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(2); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"ON"}', {"retain": false, qos: 0}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", '{"state":"ON"}', {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function)); }); it('Should publish state change of all members when a group changes its state, filtered', async () => { @@ -323,11 +324,11 @@ describe('Groups', () => { await flushPromises(); MQTT.publish.mockClear(); - await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON', brightness: 100})); + await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON', brightness: 100})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(2); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"ON","brightness":100}', {"retain": false, qos: 0}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", '{"state":"ON"}', {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"ON","brightness":100}), {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function)); }); it('Shouldnt publish group state change when a group is not optimistic', async () => { @@ -345,7 +346,7 @@ describe('Groups', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(1); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"ON","linkquality":10}', {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"ON","linkquality":10}), {"retain": false, qos: 0}, expect.any(Function)); }); it('Should publish state change of another group with shared device when a group changes its state', async () => { @@ -362,12 +363,12 @@ describe('Groups', () => { await flushPromises(); MQTT.publish.mockClear(); - await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(3); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"ON"}', {"retain": false, qos: 0}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", '{"state":"ON"}', {"retain": false, qos: 0}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_2", '{"state":"ON"}', {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_2", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function)); }); it('Should not publish state change off if any lights within are still on when changed via device', async () => { @@ -384,14 +385,14 @@ describe('Groups', () => { await controller.start(); await flushPromises(); - await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'})); await flushPromises(); MQTT.publish.mockClear(); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: 'OFF'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(1); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"OFF","brightness":0}', {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"OFF","brightness":0}), {"retain": false, qos: 0}, expect.any(Function)); }); it('Should not publish state change off if any lights within are still on when changed via shared group', async () => { @@ -409,15 +410,15 @@ describe('Groups', () => { await controller.start(); await flushPromises(); - await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'})); await flushPromises(); MQTT.publish.mockClear(); - await MQTT.events.message('zigbee2mqtt/group_2/set', JSON.stringify({state: 'OFF'})); + await MQTT.events.message('zigbee2mqtt/group_2/set', stringify({state: 'OFF'})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(2); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_2", '{"state":"OFF","brightness":0}', {"retain": false, qos: 0}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"OFF","brightness":0}', {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_2", stringify({"state":"OFF","brightness":0}), {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"OFF","brightness":0}), {"retain": false, qos: 0}, expect.any(Function)); }); it('Should publish state change off if all lights within turn off', async () => { @@ -434,17 +435,17 @@ describe('Groups', () => { await controller.start(); await flushPromises(); - await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'})); await flushPromises(); MQTT.publish.mockClear(); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: 'OFF'})); - await MQTT.events.message('zigbee2mqtt/bulb/set', JSON.stringify({state: 'OFF'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'})); + await MQTT.events.message('zigbee2mqtt/bulb/set', stringify({state: 'OFF'})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(3); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"OFF","brightness":0}', {"retain": false, qos: 0}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", '{"state":"OFF","brightness":0}', {"retain": true, qos: 0}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", '{"state":"OFF","brightness":0}', {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"OFF","brightness":0}), {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({"state":"OFF","brightness":0}), {"retain": true, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"OFF","brightness":0}), {"retain": false, qos: 0}, expect.any(Function)); }); it('Should publish state change off even when missing current state', async () => { @@ -461,17 +462,17 @@ describe('Groups', () => { await controller.start(); await flushPromises(); - await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'})); await flushPromises(); MQTT.publish.mockClear(); controller.state.state = {}; - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: 'OFF'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(2); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", '{"state":"OFF","brightness":0}', {"retain": false, qos: 0}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", '{"state":"OFF","brightness":0}', {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bulb_color", stringify({"state":"OFF","brightness":0}), {"retain": false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"OFF","brightness":0}), {"retain": false, qos: 0}, expect.any(Function)); }); it('Add to group via MQTT', async () => { @@ -483,14 +484,14 @@ describe('Groups', () => { await controller.start(); await flushPromises(); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: 'bulb_color'})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: 'bulb_color'})); await flushPromises(); expect(group.members).toStrictEqual([endpoint]); expect(settings.getGroup('group_1').devices).toStrictEqual([`${device.ieeeAddr}/1`]); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/add', - JSON.stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"ok"}), + stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -505,14 +506,14 @@ describe('Groups', () => { endpoint.addToGroup.mockImplementationOnce(() => {throw new Error('timeout')}); await flushPromises(); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: 'bulb_color'})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: 'bulb_color'})); await flushPromises(); expect(group.members).toStrictEqual([]); expect(settings.getGroup('group_1').devices).toStrictEqual([]); expect(MQTT.publish).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/add', - JSON.stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"error","error":"Failed to add from group (timeout)"}), + stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"error","error":"Failed to add from group (timeout)"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -526,14 +527,14 @@ describe('Groups', () => { await controller.start(); await flushPromises(); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group/with/slashes', device: 'bulb_color'})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group/with/slashes', device: 'bulb_color'})); await flushPromises(); expect(group.members).toStrictEqual([endpoint]); expect(settings.getGroup('group/with/slashes').devices).toStrictEqual([`${device.ieeeAddr}/1`]); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/add', - JSON.stringify({"data":{"device":"bulb_color","group":"group/with/slashes"},"status":"ok"}), + stringify({"data":{"device":"bulb_color","group":"group/with/slashes"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -546,14 +547,14 @@ describe('Groups', () => { await controller.start(); await flushPromises(); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: 'wall_switch_double/right'})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: 'wall_switch_double/right'})); await flushPromises(); expect(group.members).toStrictEqual([endpoint]); expect(settings.getGroup('group_1').devices).toStrictEqual([`${device.ieeeAddr}/${endpoint.ID}`]); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/add', - JSON.stringify({"data":{"device":"wall_switch_double/right","group":"group_1"},"status":"ok"}), + stringify({"data":{"device":"wall_switch_double/right","group":"group_1"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -566,16 +567,16 @@ describe('Groups', () => { await controller.start(); await flushPromises(); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: 'wall_switch_double/right'})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: 'wall_switch_double/right'})); await flushPromises(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: '0x0017880104e45542/3'})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: '0x0017880104e45542/3'})); await flushPromises(); expect(group.members).toStrictEqual([endpoint]); expect(settings.getGroup('group_1').devices).toStrictEqual([`${device.ieeeAddr}/${endpoint.ID}`]); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/add', - JSON.stringify({"data":{"device":"wall_switch_double/right","group":"group_1"},"status":"ok"}), + stringify({"data":{"device":"wall_switch_double/right","group":"group_1"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -589,14 +590,14 @@ describe('Groups', () => { await controller.start(); await flushPromises(); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1', device: 'bulb_color'})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1', device: 'bulb_color'})); await flushPromises(); expect(group.members).toStrictEqual([]); expect(settings.getGroup('group_1').devices).toStrictEqual([]); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/remove', - JSON.stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"ok"}), + stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -610,14 +611,14 @@ describe('Groups', () => { await controller.start(); await flushPromises(); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1', device: 'bulb_color'})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1', device: 'bulb_color'})); await flushPromises(); expect(group.members).toStrictEqual([]); expect(settings.getGroup('group_1').devices).toStrictEqual(['dummy']); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/remove', - JSON.stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"ok"}), + stringify({"data":{"device":"bulb_color","group":"group_1"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -631,14 +632,14 @@ describe('Groups', () => { await controller.start(); await flushPromises(); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1', device: '0x0017880104e45542/3'})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1', device: '0x0017880104e45542/3'})); await flushPromises(); expect(group.members).toStrictEqual([]); expect(settings.getGroup('group_1').devices).toStrictEqual([]); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/remove', - JSON.stringify({"data":{"device":"0x0017880104e45542/3","group":"group_1"},"status":"ok"}), + stringify({"data":{"device":"0x0017880104e45542/3","group":"group_1"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -652,14 +653,14 @@ describe('Groups', () => { await controller.start(); await flushPromises(); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1', device: 'wall_switch_double/3'})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1', device: 'wall_switch_double/3'})); await flushPromises(); expect(group.members).toStrictEqual([]); expect(settings.getGroup('group_1').devices).toStrictEqual([]); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/remove', - JSON.stringify({"data":{"device":"wall_switch_double/3","group":"group_1"},"status":"ok"}), + stringify({"data":{"device":"wall_switch_double/3","group":"group_1"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -673,14 +674,14 @@ describe('Groups', () => { await controller.start(); await flushPromises(); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1', device: '0x0017880104e45542/right'})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1', device: '0x0017880104e45542/right'})); await flushPromises(); expect(group.members).toStrictEqual([]); expect(settings.getGroup('group_1').devices).toStrictEqual([]); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/remove', - JSON.stringify({"data":{"device":"0x0017880104e45542/right","group":"group_1"},"status":"ok"}), + stringify({"data":{"device":"0x0017880104e45542/right","group":"group_1"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -694,14 +695,14 @@ describe('Groups', () => { await controller.start(); await flushPromises(); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove_all', JSON.stringify({device: '0x0017880104e45542/right'})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove_all', stringify({device: '0x0017880104e45542/right'})); await flushPromises(); expect(group.members).toStrictEqual([]); expect(settings.getGroup('group_1').devices).toStrictEqual([]); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/remove_all', - JSON.stringify({"data":{"device":"0x0017880104e45542/right"},"status":"ok"}), + stringify({"data":{"device":"0x0017880104e45542/right"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -711,12 +712,12 @@ describe('Groups', () => { await flushPromises(); logger.error.mockClear(); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', JSON.stringify({group: 'group_1_not_existing', device: 'bulb_color'})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1_not_existing', device: 'bulb_color'})); await flushPromises(); expect(MQTT.publish).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/remove', - JSON.stringify({"data":{"device":"bulb_color","group":"group_1_not_existing"},"status":"error","error":"Group 'group_1_not_existing' does not exist"}), + stringify({"data":{"device":"bulb_color","group":"group_1_not_existing"},"status":"error","error":"Group 'group_1_not_existing' does not exist"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -726,12 +727,12 @@ describe('Groups', () => { await flushPromises(); logger.error.mockClear(); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', JSON.stringify({group: 'group_1', device: 'bulb_color_not_existing'})); + MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: 'bulb_color_not_existing'})); await flushPromises(); expect(MQTT.publish).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/members/add', - JSON.stringify({"data":{"device":"bulb_color_not_existing","group":"group_1"},"status":"error","error":"Device 'bulb_color_not_existing' does not exist"}), + stringify({"data":{"device":"bulb_color_not_existing","group":"group_1"},"status":"error","error":"Device 'bulb_color_not_existing' does not exist"}), {retain: false, qos: 0}, expect.any(Function) ); }); diff --git a/test/homeassistant.test.js b/test/homeassistant.test.js index e27a939b..82070cf8 100644 --- a/test/homeassistant.test.js +++ b/test/homeassistant.test.js @@ -1,5 +1,6 @@ const data = require('./stub/data'); const settings = require('../lib/util/settings'); +const stringify = require('json-stable-stringify'); const logger = require('./stub/logger'); const zigbeeHerdsman = require('./stub/zigbeeHerdsman'); const flushPromises = () => new Promise(setImmediate); @@ -81,7 +82,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/temperature/config', - JSON.stringify(payload), + stringify(payload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -106,7 +107,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/humidity/config', - JSON.stringify(payload), + stringify(payload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -131,7 +132,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/pressure/config', - JSON.stringify(payload), + stringify(payload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -156,7 +157,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/battery/config', - JSON.stringify(payload), + stringify(payload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -181,7 +182,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/linkquality/config', - JSON.stringify(payload), + stringify(payload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -222,7 +223,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/temperature/config', - JSON.stringify(payload), + stringify(payload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -247,7 +248,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/humidity/config', - JSON.stringify(payload), + stringify(payload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -272,7 +273,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/pressure/config', - JSON.stringify(payload), + stringify(payload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -330,7 +331,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/temperature/config', - JSON.stringify(payload), + stringify(payload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -356,7 +357,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/humidity/config', - JSON.stringify(payload), + stringify(payload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -419,7 +420,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/fan/0x0017880104e45548/fan/config', - JSON.stringify(payload), + stringify(payload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -454,7 +455,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/cover/0x0017880104e45551/cover/config', - JSON.stringify(payload), + stringify(payload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -488,7 +489,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'my_custom_discovery_topic/sensor/0x0017880104e45522/temperature/config', - JSON.stringify(payload), + stringify(payload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -553,7 +554,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/temperature/config', - JSON.stringify(payloadHA), + stringify(payloadHA), { retain: true, qos: 0 }, expect.any(Function), ); @@ -587,13 +588,13 @@ describe('HomeAssistant extension', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', - '{"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"update_available":false}', + stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"update_available":false}), { retain: true, qos: 0 }, expect.any(Function) ); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/remote', - '{"brightness":255,"update_available":false}', + stringify({"brightness":255,"update_available":false}), { retain: true, qos: 0 }, expect.any(Function) ); @@ -613,13 +614,13 @@ describe('HomeAssistant extension', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', - '{"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"update_available":false}', + stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"update_available":false}), { retain: true, qos: 0 }, expect.any(Function) ); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/remote', - '{"brightness":255,"update_available":false}', + stringify({"brightness":255,"update_available":false}), { retain: true, qos: 0 }, expect.any(Function) ); @@ -681,7 +682,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/temperature/config', - JSON.stringify(payload), + stringify(payload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -765,7 +766,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/sensor/0x0017880104e45522/temperature/config', - JSON.stringify(payload), + stringify(payload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -797,7 +798,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/binary_sensor/0x000b57fffec6a5b2/update_available/config', - JSON.stringify(payload), + stringify(payload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -840,7 +841,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/device_automation/0x0017880104e45520/action_single/config', - JSON.stringify(discoverPayload), + stringify(discoverPayload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -854,14 +855,14 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/button', - JSON.stringify({action: "single", linkquality: 10}), + stringify({action: "single", linkquality: 10}), { retain: false, qos: 0 }, expect.any(Function), ); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/button', - JSON.stringify({linkquality: 10, action: ""}), + stringify({linkquality: 10, action: ""}), { retain: false, qos: 0 }, expect.any(Function), ); @@ -872,7 +873,7 @@ describe('HomeAssistant extension', () => { await flushPromises(); expect(MQTT.publish).not.toHaveBeenCalledWith( 'homeassistant/device_automation/0x0017880104e45520/action_single/config', - JSON.stringify(discoverPayload), + stringify(discoverPayload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -940,7 +941,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'homeassistant/device_automation/0x0017880104e45520/action_single/config', - JSON.stringify(discoverPayload), + stringify(discoverPayload), { retain: true, qos: 0 }, expect.any(Function), ); @@ -954,7 +955,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/button', - JSON.stringify({action: "single", linkquality: 10}), + stringify({action: "single", linkquality: 10}), { retain: false, qos: 0 }, expect.any(Function), ); @@ -968,11 +969,11 @@ describe('HomeAssistant extension', () => { await flushPromises(); MQTT.publish.mockClear(); - await MQTT.events.message('zigbee2mqtt/U202DST600ZB/l2/set', JSON.stringify({state: 'ON', brightness: 20})); + await MQTT.events.message('zigbee2mqtt/U202DST600ZB/l2/set', stringify({state: 'ON', brightness: 20})); await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB', JSON.stringify({state_l2:"ON", brightness_l2:20}), {"qos": 0, "retain": false}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB/l2', JSON.stringify({state:"ON", brightness:20}), {"qos": 0, "retain": false}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB/l1', JSON.stringify({}), {"qos": 0, "retain": false}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB', stringify({state_l2:"ON", brightness_l2:20}), {"qos": 0, "retain": false}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB/l2', stringify({state:"ON", brightness:20}), {"qos": 0, "retain": false}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB/l1', stringify({}), {"qos": 0, "retain": false}, expect.any(Function)); }); it('Shouldnt crash in onPublishEntityState on group publish', async () => { @@ -982,7 +983,7 @@ describe('HomeAssistant extension', () => { logger.error.mockClear(); MQTT.publish.mockClear(); - await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'})); await flushPromises(); expect(logger.error).toHaveBeenCalledTimes(0); }); diff --git a/test/legacy/bridgeLegacy.test.js b/test/legacy/bridgeLegacy.test.js index e319fa11..21010ee7 100644 --- a/test/legacy/bridgeLegacy.test.js +++ b/test/legacy/bridgeLegacy.test.js @@ -2,6 +2,7 @@ const data = require('../stub/data'); const logger = require('../stub/logger'); const zigbeeHerdsman = require('../stub/zigbeeHerdsman'); const MQTT = require('../stub/mqtt'); +const stringify = require('json-stable-stringify'); const path = require('path'); const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {}); const settings = require('../../lib/util/settings'); @@ -29,7 +30,7 @@ describe('Bridge legacy', () => { it('Should publish bridge configuration on startup', async () => { expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/config', - JSON.stringify({"version":this.version.version,"commit":this.version.commitHash,"coordinator":{"type":"z-Stack","meta":{"version":1, "revision": 20190425}},"network":{"panID":5674,"extendedPanID":[0,11,22],"channel":15},"log_level":'info',"permit_join":false}), + stringify({"version":this.version.version,"commit":this.version.commitHash,"coordinator":{"type":"z-Stack","meta":{"version":1, "revision": 20190425}},"network":{"panID":5674,"extendedPanID":[0,11,22],"channel":15},"log_level":'info',"permit_join":false}), { retain: true, qos: 0 }, expect.any(Function) ); @@ -56,7 +57,7 @@ describe('Bridge legacy', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/log', - JSON.stringify({type: "device_whitelisted", "message": {friendly_name: "bulb_color"}}), + stringify({type: "device_whitelisted", "message": {friendly_name: "bulb_color"}}), { retain: false, qos: 0 }, expect.any(Function) ); @@ -67,7 +68,7 @@ describe('Bridge legacy', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/log', - JSON.stringify({type: "device_whitelisted", "message": {friendly_name: "bulb"}}), + stringify({type: "device_whitelisted", "message": {friendly_name: "bulb"}}), { retain: false, qos: 0 }, expect.any(Function) ); @@ -85,12 +86,12 @@ describe('Bridge legacy', () => { expect(settings.getDevice('bulb_color')).toStrictEqual( {"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color", "friendly_name": "bulb_color", "retain": false} ); - MQTT.events.message('zigbee2mqtt/bridge/config/device_options', JSON.stringify({friendly_name: 'bulb_color', options: {retain: true}})); + MQTT.events.message('zigbee2mqtt/bridge/config/device_options', stringify({friendly_name: 'bulb_color', options: {retain: true}})); await flushPromises(); expect(settings.getDevice('bulb_color')).toStrictEqual( {"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color", "friendly_name": "bulb_color", "retain": true} ); - MQTT.events.message('zigbee2mqtt/bridge/config/device_options', JSON.stringify({friendly_name: 'bulb_color', optionswrong: {retain: true}})); + MQTT.events.message('zigbee2mqtt/bridge/config/device_options', stringify({friendly_name: 'bulb_color', optionswrong: {retain: true}})); await flushPromises(); expect(settings.getDevice('bulb_color')).toStrictEqual( {"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color", "friendly_name": "bulb_color", "retain": true} @@ -100,17 +101,17 @@ describe('Bridge legacy', () => { expect(settings.getDevice('bulb_color')).toStrictEqual( {"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color", "friendly_name": "bulb_color", "retain": true} ); - MQTT.events.message('zigbee2mqtt/bridge/config/device_options', JSON.stringify({friendly_name: 'bulb_color', options: {random_setting: true}})); + MQTT.events.message('zigbee2mqtt/bridge/config/device_options', stringify({friendly_name: 'bulb_color', options: {random_setting: true}})); await flushPromises(); expect(settings.getDevice('bulb_color')).toStrictEqual( {"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color", "friendly_name": "bulb_color", "random_setting": true, "retain": true} ); - MQTT.events.message('zigbee2mqtt/bridge/config/device_options', JSON.stringify({friendly_name: 'bulb_color', options: {options: {random_1: true}}})); + MQTT.events.message('zigbee2mqtt/bridge/config/device_options', stringify({friendly_name: 'bulb_color', options: {options: {random_1: true}}})); await flushPromises(); expect(settings.getDevice('bulb_color')).toStrictEqual( {"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color", "friendly_name": "bulb_color", "random_setting": true, "retain": true, options: {random_1: true}} ); - MQTT.events.message('zigbee2mqtt/bridge/config/device_options', JSON.stringify({friendly_name: 'bulb_color', options: {options: {random_2: false}}})); + MQTT.events.message('zigbee2mqtt/bridge/config/device_options', stringify({friendly_name: 'bulb_color', options: {options: {random_2: false}}})); await flushPromises(); expect(settings.getDevice('bulb_color')).toStrictEqual( {"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color", "friendly_name": "bulb_color", "random_setting": true, "retain": true, options: {random_1: true, random_2: false}} @@ -194,18 +195,18 @@ describe('Bridge legacy', () => { const bulb_color2 = {"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color2", "friendly_name": "bulb_color2", "retain": false}; MQTT.publish.mockClear(); expect(settings.getDevice('bulb_color')).toStrictEqual({"ID": "0x000b57fffec6a5b3", "friendlyName": "bulb_color", "friendly_name": "bulb_color", "retain": false}); - MQTT.events.message('zigbee2mqtt/bridge/config/rename', JSON.stringify({old: 'bulb_color', new: 'bulb_color2'})); + MQTT.events.message('zigbee2mqtt/bridge/config/rename', stringify({old: 'bulb_color', new: 'bulb_color2'})); await flushPromises(); expect(settings.getDevice('bulb_color')).toStrictEqual(null); expect(settings.getDevice('bulb_color2')).toStrictEqual(bulb_color2); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/log', - JSON.stringify({type: 'device_renamed', message: {from: 'bulb_color', to: 'bulb_color2'}}), + stringify({type: 'device_renamed', message: {from: 'bulb_color', to: 'bulb_color2'}}), {qos: 0, retain: false}, expect.any(Function) ); - MQTT.events.message('zigbee2mqtt/bridge/config/rename', JSON.stringify({old: 'bulb_color2', newmalformed: 'bulb_color3'})); + MQTT.events.message('zigbee2mqtt/bridge/config/rename', stringify({old: 'bulb_color2', newmalformed: 'bulb_color3'})); await flushPromises(); expect(settings.getDevice('bulb_color2')).toStrictEqual(bulb_color2); @@ -213,7 +214,7 @@ describe('Bridge legacy', () => { await flushPromises(); expect(settings.getDevice('bulb_color2')).toStrictEqual(bulb_color2); - MQTT.events.message('zigbee2mqtt/bridge/config/rename', JSON.stringify({old: 'bulb_color', new: 'bulb_color3'})); + MQTT.events.message('zigbee2mqtt/bridge/config/rename', stringify({old: 'bulb_color', new: 'bulb_color3'})); await flushPromises(); expect(settings.getDevice('bulb_color2')).toStrictEqual(bulb_color2); }); @@ -221,12 +222,12 @@ describe('Bridge legacy', () => { it('Should allow rename groups', async () => { MQTT.publish.mockClear(); expect(settings.getGroup(1)).toStrictEqual({"ID": 1, devices: [], friendlyName: "group_1", "friendly_name": "group_1", optimistic: true, retain: false}); - MQTT.events.message('zigbee2mqtt/bridge/config/rename', JSON.stringify({old: 'group_1', new: 'group_1_renamed'})); + MQTT.events.message('zigbee2mqtt/bridge/config/rename', stringify({old: 'group_1', new: 'group_1_renamed'})); await flushPromises(); expect(settings.getGroup(1)).toStrictEqual({"ID": 1, devices: [], friendlyName: "group_1_renamed", "friendly_name": "group_1_renamed", optimistic: true, retain: false}); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/log', - JSON.stringify({type: 'group_renamed', message: {from: 'group_1', to: 'group_1_renamed'}}), + stringify({type: 'group_renamed', message: {from: 'group_1', to: 'group_1_renamed'}}), {qos: 0, retain: false}, expect.any(Function) ); @@ -243,7 +244,7 @@ describe('Bridge legacy', () => { expect(settings.getDevice('0x000b57fffec6a5b2').friendlyName).toStrictEqual('bulb_new_name'); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/log', - JSON.stringify({type: 'device_renamed', message: {from: 'bulb', to: 'bulb_new_name'}}), + stringify({type: 'device_renamed', message: {from: 'bulb', to: 'bulb_new_name'}}), {qos: 0, retain: false}, expect.any(Function) ); @@ -265,7 +266,7 @@ describe('Bridge legacy', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/log', - JSON.stringify({type: 'group_added', message: 'new_group'}), + stringify({type: 'group_added', message: 'new_group'}), {qos: 0, retain: false}, expect.any(Function) ); @@ -283,7 +284,7 @@ describe('Bridge legacy', () => { expect(zigbeeHerdsman.createGroup).toHaveBeenCalledWith(3); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/log', - JSON.stringify({type: 'group_added', message: 'new_group'}), + stringify({type: 'group_added', message: 'new_group'}), {qos: 0, retain: false}, expect.any(Function) ); @@ -298,7 +299,7 @@ describe('Bridge legacy', () => { expect(zigbeeHerdsman.createGroup).toHaveBeenCalledWith(42); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/log', - JSON.stringify({type: 'group_added', message: 'new_group'}), + stringify({type: 'group_added', message: 'new_group'}), {qos: 0, retain: false}, expect.any(Function) ); @@ -321,7 +322,7 @@ describe('Bridge legacy', () => { expect(group.removeFromNetwork).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/log', - JSON.stringify({type: 'group_removed', message: 'group_1'}), + stringify({type: 'group_removed', message: 'group_1'}), {qos: 0, retain: false}, expect.any(Function) ); @@ -335,7 +336,7 @@ describe('Bridge legacy', () => { expect(group.removeFromDatabase).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/log', - JSON.stringify({type: 'group_removed', message: 'group_1'}), + stringify({type: 'group_removed', message: 'group_1'}), {qos: 0, retain: false}, expect.any(Function) ); @@ -370,7 +371,7 @@ describe('Bridge legacy', () => { expect(settings.getDevice('bulb_color')).toBeNull(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/log', - JSON.stringify({type: 'device_removed', message: 'bulb_color'}), + stringify({type: 'device_removed', message: 'bulb_color'}), {qos: 0, retain: false}, expect.any(Function) ); @@ -392,7 +393,7 @@ describe('Bridge legacy', () => { expect(settings.getDevice('bulb_color')).toBeNull(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/log', - JSON.stringify({type: 'device_force_removed', message: 'bulb_color'}), + stringify({type: 'device_force_removed', message: 'bulb_color'}), {qos: 0, retain: false}, expect.any(Function) ); @@ -413,7 +414,7 @@ describe('Bridge legacy', () => { expect(settings.getDevice('bulb_color')).toBeNull(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/log', - JSON.stringify({type: 'device_banned', message: 'bulb_color'}), + stringify({type: 'device_banned', message: 'bulb_color'}), {qos: 0, retain: false}, expect.any(Function) ); diff --git a/test/networkMap.test.js b/test/networkMap.test.js index 26a148e9..533a4494 100644 --- a/test/networkMap.test.js +++ b/test/networkMap.test.js @@ -1,6 +1,7 @@ const data = require('./stub/data'); const logger = require('./stub/logger'); const zigbeeHerdsman = require('./stub/zigbeeHerdsman'); +const stringify = require('json-stable-stringify'); const {coordinator, bulb, bulb_color, WXKG02LM, CC2530_ROUTER, unsupported_router} = zigbeeHerdsman.devices; @@ -258,7 +259,7 @@ describe('Networkmap', () => { it('Should output raw networkmap', async () => { mock(); MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/networkmap', JSON.stringify({type: 'raw', routes: true})); + MQTT.events.message('zigbee2mqtt/bridge/request/networkmap', stringify({type: 'raw', routes: true})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(1); let call = MQTT.publish.mock.calls[0]; @@ -303,7 +304,7 @@ describe('Networkmap', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/networkmap', - JSON.stringify({"data":{},"status":"error","error":"Type 'not_existing' not supported, allowed are: raw,graphviz,plantuml"}), + stringify({"data":{},"status":"error","error":"Type 'not_existing' not supported, allowed are: raw,graphviz,plantuml"}), {retain: false, qos: 0}, expect.any(Function) ); }); diff --git a/test/otaUpdate.test.js b/test/otaUpdate.test.js index 09074756..7d0050e8 100644 --- a/test/otaUpdate.test.js +++ b/test/otaUpdate.test.js @@ -6,6 +6,7 @@ const settings = require('../lib/util/settings'); const Controller = require('../lib/controller'); const flushPromises = () => new Promise(setImmediate); const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters'); +const stringify = require('json-stable-stringify'); describe('OTA update', () => { let controller; @@ -58,28 +59,28 @@ describe('OTA update', () => { expect(mapped.ota.updateToLatest).toHaveBeenCalledWith(device, logger, expect.any(Function)); expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 0.00%`); expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 10.00%, +- 60 minutes remaining`); - expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb', from '{"softwareBuildID":1,"dateCode":"20190101"}' to '{"softwareBuildID":2,"dateCode":"20190102"}'`); + expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb', from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190102","softwareBuildID":2}'`); expect(device.save).toHaveBeenCalledTimes(1); expect(device.dateCode).toBe('20190102'); expect(device.softwareBuildID).toBe(2); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', - JSON.stringify({"update_available":false,"update":{"state":"updating","progress":0}}), + stringify({"update_available":false,"update":{"state":"updating","progress":0}}), {retain: true, qos: 0}, expect.any(Function) ); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', - JSON.stringify({"update_available":false,"update":{"state":"updating","progress":10,"remaining":3600}}), + stringify({"update_available":false,"update":{"state":"updating","progress":10,"remaining":3600}}), {retain: true, qos: 0}, expect.any(Function) ); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', - JSON.stringify({"update_available":false,"update":{"state":"idle"}}), + stringify({"update_available":false,"update":{"state":"idle"}}), {retain: true, qos: 0}, expect.any(Function) ); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/ota_update/update', - JSON.stringify({"data":{"id": "bulb","from":{"software_build_id":1,"date_code":"20190101"},"to":{"software_build_id":2,"date_code":"20190102"}},"status":"ok"}), + stringify({"data":{"id": "bulb","from":{"software_build_id":1,"date_code":"20190101"},"to":{"software_build_id":2,"date_code":"20190102"}},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -95,16 +96,16 @@ describe('OTA update', () => { throw new Error('Update failed'); }); - MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/update', JSON.stringify({id: "bulb"})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/update', stringify({id: "bulb"})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', - JSON.stringify({"update_available":true,"update":{"state":"available"}}), + stringify({"update_available":true,"update":{"state":"available"}}), {retain: true, qos: 0}, expect.any(Function) ); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/ota_update/update', - JSON.stringify({"data":{"id": "bulb"},"status":"error","error":"Update of 'bulb' failed (Update failed)"}), + stringify({"data":{"id": "bulb"},"status":"error","error":"Update of 'bulb' failed (Update failed)"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -121,7 +122,7 @@ describe('OTA update', () => { expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/ota_update/check', - JSON.stringify({"data":{"id": "bulb","updateAvailable":false},"status":"ok"}), + stringify({"data":{"id": "bulb","updateAvailable":false},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); @@ -133,7 +134,7 @@ describe('OTA update', () => { expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/ota_update/check', - JSON.stringify({"data":{"id": "bulb","updateAvailable":true},"status":"ok"}), + stringify({"data":{"id": "bulb","updateAvailable":true},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -150,7 +151,7 @@ describe('OTA update', () => { expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/ota_update/check', - JSON.stringify({"data":{"id": "bulb"},"status":"error","error": `Failed to check if update available for 'bulb' (RF singals disturbed because of dogs barking)`}), + stringify({"data":{"id": "bulb"},"status":"error","error": `Failed to check if update available for 'bulb' (RF singals disturbed because of dogs barking)`}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -160,7 +161,7 @@ describe('OTA update', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/ota_update/check', - JSON.stringify({"data":{"id": "not_existing_deviceooo"},"status":"error","error": `Device 'not_existing_deviceooo' does not exist`}), + stringify({"data":{"id": "not_existing_deviceooo"},"status":"error","error": `Device 'not_existing_deviceooo' does not exist`}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -170,7 +171,7 @@ describe('OTA update', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/ota_update/check', - JSON.stringify({"data":{"id": "ZNLDP12LM"},"status":"error","error": `Device 'ZNLDP12LM' does not support OTA updates`}), + stringify({"data":{"id": "ZNLDP12LM"},"status":"error","error": `Device 'ZNLDP12LM' does not support OTA updates`}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -193,7 +194,7 @@ describe('OTA update', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/ota_update/check', - JSON.stringify({"data":{"id": "bulb"},"status":"error","error": `Update or check for update already in progress for 'bulb'`}), + stringify({"data":{"id": "bulb"},"status":"error","error": `Update or check for update already in progress for 'bulb'`}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -210,7 +211,7 @@ describe('OTA update', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/ota_update/update', - JSON.stringify({"data":{"id":"bulb","from":null,"to":null},"status":"ok"}), + stringify({"data":{"id":"bulb","from":null,"to":null},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -245,7 +246,7 @@ describe('OTA update', () => { expect(logger.info).not.toHaveBeenCalledWith(`Update available for 'bulb'`); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', - JSON.stringify({"update_available":true,"update":{"state":"available"}}), + stringify({"update_available":true,"update":{"state":"available"}}), {retain: true, qos: 0}, expect.any(Function) ); }); @@ -297,7 +298,7 @@ describe('OTA update', () => { expect(mapped.ota.updateToLatest).toHaveBeenCalledWith(device, logger, expect.any(Function)); expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 0.00%`); expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 10.00%, +- 60 minutes remaining`); - expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb', from '{"softwareBuildID":1,"dateCode":"20190101"}' to '{"softwareBuildID":2,"dateCode":"20190102"}'`); + expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb', from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190102","softwareBuildID":2}'`); expect(logger.error).toHaveBeenCalledTimes(0); expect(device.save).toHaveBeenCalledTimes(1); expect(device.dateCode).toBe('20190102'); diff --git a/test/publish.test.js b/test/publish.test.js index d0e248b2..1d94318b 100644 --- a/test/publish.test.js +++ b/test/publish.test.js @@ -2,6 +2,7 @@ const data = require('./stub/data'); const logger = require('./stub/logger'); const zigbeeHerdsman = require('./stub/zigbeeHerdsman'); const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters'); +const stringify = require('json-stable-stringify'); const MQTT = require('./stub/mqtt'); const settings = require('../lib/util/settings'); const Controller = require('../lib/controller'); @@ -55,7 +56,7 @@ describe('Publish', () => { it('Should publish messages to zigbee devices', async () => { const endpoint = zigbeeHerdsman.devices.bulb_color.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({brightness: '200'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({brightness: '200'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("genLevelCtrl", "moveToLevelWithOnOff", {"level": 200, "transtime": 0}, {}); @@ -66,13 +67,13 @@ describe('Publish', () => { }); it('Should publish messages to zigbee devices when there is no converters', async () => { - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({brightness_no: '200'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({brightness_no: '200'})); await flushPromises(); expectNothingPublished(); }); it('Should publish messages to zigbee devices when there is a get converter but no set', async () => { - await MQTT.events.message('zigbee2mqtt/thermostat/set', JSON.stringify({relay_status_log_rsp: '200'})); + await MQTT.events.message('zigbee2mqtt/thermostat/set', stringify({relay_status_log_rsp: '200'})); await flushPromises(); expectNothingPublished(); }); @@ -81,7 +82,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.bulb_color; settings.set(['devices', device.ieeeAddr, 'friendly_name'], 'wohnzimmer.light.wall.right') const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/wohnzimmer.light.wall.right/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/wohnzimmer.light.wall.right/set', stringify({state: 'ON'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("genOnOff", "on", {}, {}); @@ -94,7 +95,7 @@ describe('Publish', () => { it('Should publish messages to zigbee devices when brightness is in %', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({brightness_percent: '92'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({brightness_percent: '92'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("genLevelCtrl", "moveToLevelWithOnOff", {level: 235, transtime: 0}, {}); @@ -107,7 +108,7 @@ describe('Publish', () => { it('Should publish messages to zigbee devices when brightness is in number', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({brightness: 230})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({brightness: 230})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("genLevelCtrl", "moveToLevelWithOnOff", {level: 230, transtime: 0}, {}); @@ -120,7 +121,7 @@ describe('Publish', () => { it('Should publish messages to zigbee devices with color_temp', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({color_temp: '222'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({color_temp: '222'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("lightingColorCtrl", "moveToColorTemp", {colortemp: 222, transtime: 0}, {}); @@ -133,7 +134,7 @@ describe('Publish', () => { it('Should publish messages to zigbee devices with color_temp in %', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({color_temp_percent: '100'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({color_temp_percent: '100'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("lightingColorCtrl", "moveToColorTemp", {colortemp: 500, transtime: 0}, {}); @@ -146,7 +147,7 @@ describe('Publish', () => { it('Should publish messages to zigbee devices with non-default ep', async () => { const device = zigbeeHerdsman.devices.QBKG04LM; const endpoint = device.getEndpoint(2); - await MQTT.events.message('zigbee2mqtt/wall_switch/set', JSON.stringify({state: 'OFF'})); + await MQTT.events.message('zigbee2mqtt/wall_switch/set', stringify({state: 'OFF'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("genOnOff", "off", {}, {}); @@ -159,7 +160,7 @@ describe('Publish', () => { it('Should publish messages to zigbee devices with non-default ep and postfix', async () => { const device = zigbeeHerdsman.devices.QBKG03LM; const endpoint = device.getEndpoint(3); - await MQTT.events.message('zigbee2mqtt/wall_switch_double/right/set', JSON.stringify({state: 'OFF'})); + await MQTT.events.message('zigbee2mqtt/wall_switch_double/right/set', stringify({state: 'OFF'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("genOnOff", "off", {}, {}); @@ -172,7 +173,7 @@ describe('Publish', () => { it('Should publish messages to zigbee devices to non default-ep with state_[EP]', async () => { const device = zigbeeHerdsman.devices.QBKG03LM; const endpoint = device.getEndpoint(3); - await MQTT.events.message('zigbee2mqtt/wall_switch_double/set', JSON.stringify({state_right: 'OFF'})); + await MQTT.events.message('zigbee2mqtt/wall_switch_double/set', stringify({state_right: 'OFF'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("genOnOff", "off", {}, {}); @@ -185,7 +186,7 @@ describe('Publish', () => { it('Should publish messages to zigbee devices with color xy', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({color: {x: 100, y: 50}})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({color: {x: 100, y: 50}})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("lightingColorCtrl", "moveToColor", {colorx: 6553500, colory: 3276750, transtime: 0}, {}); @@ -198,7 +199,7 @@ describe('Publish', () => { it('Should publish messages to zigbee devices with color xy and state', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({color: {x: 100, y: 50}, state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({color: {x: 100, y: 50}, state: 'ON'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(2); expect(endpoint.command).toHaveBeenCalledWith("genOnOff", "on", {}, {}); @@ -215,7 +216,7 @@ describe('Publish', () => { it('Should publish messages to zigbee devices with color xy and brightness', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({color: {x: 100, y: 50}, brightness: 20})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({color: {x: 100, y: 50}, brightness: 20})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(2); expect(endpoint.command).toHaveBeenCalledWith("genLevelCtrl", "moveToLevelWithOnOff", {level: 20, transtime: 0}, {}); @@ -232,7 +233,7 @@ describe('Publish', () => { it('Should publish messages to zigbee devices with color xy, brightness and state on', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({color: {x: 100, y: 50}, brightness: 20, state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({color: {x: 100, y: 50}, brightness: 20, state: 'ON'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(2); expect(endpoint.command).toHaveBeenCalledWith("genLevelCtrl", "moveToLevelWithOnOff", {level: 20, transtime: 0}, {}); @@ -249,7 +250,7 @@ describe('Publish', () => { it('Should publish messages to zigbee devices with color xy, brightness and state off', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({color: {x: 100, y: 50}, brightness: 20, state: 'OFF'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({color: {x: 100, y: 50}, brightness: 20, state: 'OFF'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(2); expect(endpoint.command).toHaveBeenNthCalledWith(1, "lightingColorCtrl", "moveToColor", {colorx: 6553500, colory: 3276750, transtime: 0}, {}); @@ -266,7 +267,7 @@ describe('Publish', () => { it('Should publish messages to zigbee devices with color rgb', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({color: {r: 100, g: 200, b: 10}})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({color: {r: 100, g: 200, b: 10}})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("lightingColorCtrl", "moveToColor", {colorx: 16764, colory: 40979, transtime: 0}, {}); @@ -278,7 +279,7 @@ describe('Publish', () => { it('Should publish messages to zigbee devices with color rgb', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({color: {rgb: '100,200,10'}})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({color: {rgb: '100,200,10'}})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("lightingColorCtrl", "moveToColor", {colorx: 16764, colory: 40979, transtime: 0}, {}); @@ -290,7 +291,7 @@ describe('Publish', () => { it('Should publish messages to zigbee devices with color rgb', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: 'ON', brightness: '50'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON', brightness: '50'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("genLevelCtrl", "moveToLevelWithOnOff", {level: 50, transtime: 0}, {}); @@ -301,7 +302,7 @@ describe('Publish', () => { it('Should publish messages groups', async () => { const group = zigbeeHerdsman.groups.group_1; - await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'})); await flushPromises(); expect(group.command).toHaveBeenCalledTimes(1); expect(group.command).toHaveBeenCalledWith("genOnOff", "on", {}, {}); @@ -312,7 +313,7 @@ describe('Publish', () => { it('Should publish messages to groups with brightness_percent', async () => { const group = zigbeeHerdsman.groups.group_1; - await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({brightness_percent: 50})); + await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({brightness_percent: 50})); await flushPromises(); expect(group.command).toHaveBeenCalledTimes(1); expect(group.command).toHaveBeenCalledWith("genLevelCtrl", "moveToLevelWithOnOff", {level: 127, transtime: 0}, {}); @@ -323,7 +324,7 @@ describe('Publish', () => { it('Should publish messages to groups with on and brightness', async () => { const group = zigbeeHerdsman.groups.group_1; - await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON', brightness: 50})); + await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON', brightness: 50})); await flushPromises(); expect(group.command).toHaveBeenCalledTimes(1); expect(group.command).toHaveBeenCalledWith("genLevelCtrl", "moveToLevelWithOnOff", {level: 50, transtime: 0}, {}); @@ -334,7 +335,7 @@ describe('Publish', () => { it('Should publish messages to groups with off and brightness', async () => { const group = zigbeeHerdsman.groups.group_1; - await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'OFF', brightness: 50})); + await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'OFF', brightness: 50})); await flushPromises(); expect(group.command).toHaveBeenCalledTimes(1); expect(group.command).toHaveBeenCalledWith("genOnOff", "off", {}, {}); @@ -345,7 +346,7 @@ describe('Publish', () => { it('Should publish messages to groups color', async () => { const group = zigbeeHerdsman.groups.group_1; - await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({color: {x: 0.37, y: 0.28}})); + await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({color: {x: 0.37, y: 0.28}})); await flushPromises(); expect(group.command).toHaveBeenCalledTimes(1); expect(group.command).toHaveBeenCalledWith("lightingColorCtrl", "moveToColor", {colorx: 24248, colory: 18350, transtime: 0}, {}); @@ -356,7 +357,7 @@ describe('Publish', () => { it('Should publish messages to groups color temperature', async () => { const group = zigbeeHerdsman.groups.group_1; - await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({color_temp: 100})); + await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({color_temp: 100})); await flushPromises(); expect(group.command).toHaveBeenCalledTimes(1); expect(group.command).toHaveBeenCalledWith("lightingColorCtrl", "moveToColorTemp", {colortemp: 100, transtime: 0}, {}); @@ -368,7 +369,7 @@ describe('Publish', () => { it('Should create and publish to group which is in configuration.yaml but not in zigbee-herdsman', async () => { delete zigbeeHerdsman.groups.group_2; expect(Object.values(zigbeeHerdsman.groups).length).toBe(4); - await MQTT.events.message('zigbee2mqtt/group_2/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/group_2/set', stringify({state: 'ON'})); await flushPromises(); expect(Object.values(zigbeeHerdsman.groups).length).toBe(5); expect(zigbeeHerdsman.groups.group_2.command).toHaveBeenCalledTimes(1); @@ -376,31 +377,31 @@ describe('Publish', () => { }); it('Should handle non-valid topics', async () => { - await MQTT.events.message('zigbee2mqtt1/bulb_color/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt1/bulb_color/set', stringify({state: 'ON'})); await flushPromises(); expectNothingPublished(); }); it('Should handle non-valid topics', async () => { - await MQTT.events.message('zigbee2mqtt1/bulb_color/sett', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt1/bulb_color/sett', stringify({state: 'ON'})); await flushPromises(); expectNothingPublished(); }); it('Should handle non-valid topics', async () => { - await MQTT.events.message('zigbee2mqtt/bulb_color/write', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/write', stringify({state: 'ON'})); await flushPromises(); expectNothingPublished(); }); it('Should handle non-valid topics', async () => { - await MQTT.events.message('zigbee2mqtt/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/set', stringify({state: 'ON'})); await flushPromises(); expectNothingPublished(); }); it('Should handle non-valid topics', async () => { - await MQTT.events.message('set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('set', stringify({state: 'ON'})); await flushPromises(); expectNothingPublished(); }); @@ -408,7 +409,7 @@ describe('Publish', () => { it('Should handle get', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/get', JSON.stringify({state: ''})); + await MQTT.events.message('zigbee2mqtt/bulb_color/get', stringify({state: ''})); await flushPromises(); expect(endpoint.read).toHaveBeenCalledTimes(1); expect(endpoint.read).toHaveBeenCalledWith('genOnOff', ['onOff']); @@ -418,7 +419,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.QBKG03LM; const endpoint2 = device.getEndpoint(2); const endpoint3 = device.getEndpoint(3); - await MQTT.events.message('zigbee2mqtt/0x0017880104e45542/get', JSON.stringify({state_left: '', state_right: ''})); + await MQTT.events.message('zigbee2mqtt/0x0017880104e45542/get', stringify({state_left: '', state_right: ''})); await flushPromises(); expect(endpoint2.read).toHaveBeenCalledTimes(1); expect(endpoint2.read).toHaveBeenCalledWith('genOnOff', ['onOff']); @@ -431,7 +432,7 @@ describe('Publish', () => { const endpoint2 = device.getEndpoint(2); const endpoint3 = device.getEndpoint(3); logger.error.mockClear(); - await MQTT.events.message('zigbee2mqtt/0x0017880104e45542/get', JSON.stringify({state_center: '', state_right: ''})); + await MQTT.events.message('zigbee2mqtt/0x0017880104e45542/get', stringify({state_center: '', state_right: ''})); await flushPromises(); expect(logger.error).toHaveBeenCalledWith(`Device 'wall_switch_double' has no endpoint 'center'`); expect(endpoint2.read).toHaveBeenCalledTimes(0); @@ -440,19 +441,19 @@ describe('Publish', () => { }); it('Should not respond to bridge/config/devices/get', async () => { - await MQTT.events.message('zigbee2mqtt/bridge/config/devices/get', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/bridge/config/devices/get', stringify({state: 'ON'})); await flushPromises(); expectNothingPublished(); }); it('Should not respond to bridge/config/devices/set', async () => { - await MQTT.events.message('zigbee2mqtt/bridge/config/devices/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/bridge/config/devices/set', stringify({state: 'ON'})); await flushPromises(); expectNothingPublished(); }); it('Should not respond to bridge/config/devices', async () => { - await MQTT.events.message('zigbee2mqtt/bridge/config/devices', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/bridge/config/devices', stringify({state: 'ON'})); await flushPromises(); expectNothingPublished(); }); @@ -461,7 +462,7 @@ describe('Publish', () => { settings.set(['mqtt', 'base_topic'], 'zigbee2mqtt/at/my/home'); const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/at/my/home/bulb_color/get', JSON.stringify({state: ''})); + await MQTT.events.message('zigbee2mqtt/at/my/home/bulb_color/get', stringify({state: ''})); await flushPromises(); expect(endpoint.read).toHaveBeenCalledTimes(1); expect(endpoint.read).toHaveBeenCalledWith('genOnOff', ['onOff']); @@ -471,7 +472,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.bulb_color; settings.set(['devices', device.ieeeAddr, 'friendly_name'], 'floor0/basement/my_device_id2'); const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/floor0/basement/my_device_id2/get', JSON.stringify({state: ''})); + await MQTT.events.message('zigbee2mqtt/floor0/basement/my_device_id2/get', stringify({state: ''})); await flushPromises(); expect(endpoint.read).toHaveBeenCalledTimes(1); expect(endpoint.read).toHaveBeenCalledWith('genOnOff', ['onOff']); @@ -482,7 +483,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.bulb_color; settings.set(['devices', device.ieeeAddr, 'friendly_name'], 'floor0/basement/my_device_id2'); const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/at/my/basement/floor0/basement/my_device_id2/get', JSON.stringify({state: ''})); + await MQTT.events.message('zigbee2mqtt/at/my/basement/floor0/basement/my_device_id2/get', stringify({state: ''})); await flushPromises(); expect(endpoint.read).toHaveBeenCalledTimes(1); expect(endpoint.read).toHaveBeenCalledWith('genOnOff', ['onOff']); @@ -512,14 +513,14 @@ describe('Publish', () => { it('Should parse set with ieeeAddr topic', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/0x000b57fffec6a5b3/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/0x000b57fffec6a5b3/set', stringify({state: 'ON'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("genOnOff", "on", {}, {}); }); it('Should parse set with non-existing postfix', async () => { - await MQTT.events.message('zigbee2mqtt/wall_switch_double/invalid/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/wall_switch_double/invalid/set', stringify({state: 'ON'})); await flushPromises(); expectNothingPublished(); }); @@ -529,7 +530,7 @@ describe('Publish', () => { const endpoint = device.getEndpoint(1); // Non-inverted (open = 100, close = 0) - await MQTT.events.message('zigbee2mqtt/J1_cover/set', JSON.stringify({position: 90})); + await MQTT.events.message('zigbee2mqtt/J1_cover/set', stringify({position: 90})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("closuresWindowCovering", "goToLiftPercentage", {percentageliftvalue: 10}, {}); @@ -537,7 +538,7 @@ describe('Publish', () => { // // Inverted endpoint.command.mockClear(); settings.set(['devices', device.ieeeAddr, 'invert_cover'], true); - await MQTT.events.message('zigbee2mqtt/J1_cover/set', JSON.stringify({position: 90})); + await MQTT.events.message('zigbee2mqtt/J1_cover/set', stringify({position: 90})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("closuresWindowCovering", "goToLiftPercentage", {percentageliftvalue: 90}, {}); @@ -554,8 +555,8 @@ describe('Publish', () => { expect(endpoint.command).toHaveBeenCalledWith("genOnOff", "on", {}, {}); expect(endpoint.command).toHaveBeenCalledWith("genOnOff", "toggle", {}, {}); expect(MQTT.publish).toHaveBeenCalledTimes(2); - expect(MQTT.publish.mock.calls[0]).toEqual(["zigbee2mqtt/wall_switch_double", JSON.stringify({state_left: 'ON'}), {"qos": 0, "retain": false}, expect.any(Function)]); - expect(MQTT.publish.mock.calls[1]).toEqual(["zigbee2mqtt/wall_switch_double", JSON.stringify({state_left: 'OFF'}), {"qos": 0, "retain": false}, expect.any(Function)]); + expect(MQTT.publish.mock.calls[0]).toEqual(["zigbee2mqtt/wall_switch_double", stringify({state_left: 'ON'}), {"qos": 0, "retain": false}, expect.any(Function)]); + expect(MQTT.publish.mock.calls[1]).toEqual(["zigbee2mqtt/wall_switch_double", stringify({state_left: 'OFF'}), {"qos": 0, "retain": false}, expect.any(Function)]); }); it('Should parse set with postfix topic and attribute', async () => { @@ -572,7 +573,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.QBKG03LM; settings.set(['devices', device.ieeeAddr, 'friendly_name'], 'in/basement/wall_switch_double'); const endpoint = device.getEndpoint(2); - await MQTT.events.message('zigbee2mqtt/at/my/home/in/basement/wall_switch_double/left/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/at/my/home/in/basement/wall_switch_double/left/set', stringify({state: 'ON'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("genOnOff", "on", {}, {}); @@ -582,7 +583,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.QBKG03LM; settings.set(['devices', device.ieeeAddr, 'friendly_name'], 'ground_floor/kitchen/wall_switch/2'); const endpoint = device.getEndpoint(2); - await MQTT.events.message('zigbee2mqtt/ground_floor/kitchen/wall_switch/2/left/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/ground_floor/kitchen/wall_switch/2/left/set', stringify({state: 'ON'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("genOnOff", "on", {}, {}); @@ -591,8 +592,8 @@ describe('Publish', () => { it('Should not publish messages to zigbee devices when payload is invalid', async () => { const device = zigbeeHerdsman.devices.QBKG03LM; const endpoint = device.getEndpoint(2); - await MQTT.events.message('zigbee2mqtt/wall_switch_double/left/set', JSON.stringify({state: true})); - await MQTT.events.message('zigbee2mqtt/wall_switch_double/left/set', JSON.stringify({state: 1})); + await MQTT.events.message('zigbee2mqtt/wall_switch_double/left/set', stringify({state: true})); + await MQTT.events.message('zigbee2mqtt/wall_switch_double/left/set', stringify({state: 1})); await flushPromises(); expectNothingPublished(); }); @@ -601,7 +602,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); const payload = {'state': 'ON', 'color': {'x': 0.701, 'y': 0.299}}; - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(2); expect(endpoint.command.mock.calls[0]).toEqual(["genOnOff", "on", {}, {}]); @@ -614,7 +615,7 @@ describe('Publish', () => { settings.set(['devices', device.ieeeAddr, 'retrieve_state'], true); const endpoint = device.getEndpoint(1); const payload = {'state': 'ON', 'color': {'x': 0.701, 'y': 0.299}}; - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify(payload)); await flushPromises(); jest.runAllTimers(); expect(endpoint.command).toHaveBeenCalledTimes(2); @@ -628,7 +629,7 @@ describe('Publish', () => { it('Should use transition when brightness with group', async () => { const group = zigbeeHerdsman.groups.group_1; settings.set(['groups', '1', 'transition'], 20); - await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({brightness: 100})); + await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({brightness: 100})); await flushPromises(); expect(group.command).toHaveBeenCalledTimes(1); expect(group.command).toHaveBeenCalledWith("genLevelCtrl", "moveToLevelWithOnOff", {level: 100, transtime: 200}, {}); @@ -642,7 +643,7 @@ describe('Publish', () => { settings.set(['devices', device.ieeeAddr, 'transition'], 20); const endpoint = device.getEndpoint(1); const payload = {brightness: 20}; - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command.mock.calls[0]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 20, transtime: 200}, {}]); @@ -653,7 +654,7 @@ describe('Publish', () => { settings.set(['device_options'], {transition: 20}); const endpoint = device.getEndpoint(1); const payload = {brightness: 20}; - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command.mock.calls[0]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 20, transtime: 200}, {}]); @@ -663,18 +664,18 @@ describe('Publish', () => { // https://github.com/Koenkk/zigbee2mqtt/issues/3799 const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({"state": "OFF", "transition": 0.5})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({"state": "OFF", "transition": 0.5})); await flushPromises(); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({"state": "ON", "transition": 0.5})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({"state": "ON", "transition": 0.5})); await flushPromises(); expect(MQTT.publish).toHaveBeenNthCalledWith(1, 'zigbee2mqtt/bulb_color', - JSON.stringify({state: 'OFF', brightness: 0}), + stringify({state: 'OFF', brightness: 0}), {retain: false, qos: 0}, expect.any(Function) ); expect(MQTT.publish).toHaveBeenNthCalledWith(2, 'zigbee2mqtt/bulb_color', - JSON.stringify({state: 'ON', brightness: 254}), + stringify({state: 'ON', brightness: 254}), {retain: false, qos: 0}, expect.any(Function) ); expect(endpoint.command).toHaveBeenCalledTimes(2); @@ -686,11 +687,11 @@ describe('Publish', () => { // https://github.com/Koenkk/zigbee2mqtt/issues/3563 const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({"state": "ON", "brightness": 50})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({"state": "ON", "brightness": 50})); await flushPromises(); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({"state": "ON"})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({"state": "ON"})); await flushPromises(); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({"state": "ON", "transition": 1})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({"state": "ON", "transition": 1})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(3); expect(endpoint.command.mock.calls[0]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 50, transtime: 0}, {}]); @@ -703,7 +704,7 @@ describe('Publish', () => { settings.set(['devices', device.ieeeAddr, 'transition'], 20); const endpoint = device.getEndpoint(1); const payload = {color_temp: 200}; - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command.mock.calls[0]).toEqual(["lightingColorCtrl", "moveToColorTemp", {colortemp: 200, transtime: 200}, {}]); @@ -713,7 +714,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.bulb; const endpoint = device.getEndpoint(1); const payload = {state: 'ON', brightness: 20, color_temp: 200, transition: 20}; - await MQTT.events.message('zigbee2mqtt/bulb/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/bulb/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(2); expect(endpoint.command.mock.calls[0]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 20, transtime: 0}, {}]); @@ -722,7 +723,7 @@ describe('Publish', () => { it('Should use transition only once when setting brightness and color temperature for group which contains TRADFRI', async () => { const group = zigbeeHerdsman.groups.group_with_tradfri; - await MQTT.events.message('zigbee2mqtt/group_with_tradfri/set', JSON.stringify({"state": "ON", "transition": 60, "brightness": 20, "color_temp": 400})); + await MQTT.events.message('zigbee2mqtt/group_with_tradfri/set', stringify({"state": "ON", "transition": 60, "brightness": 20, "color_temp": 400})); await flushPromises(); expect(group.command).toHaveBeenCalledTimes(2); expect(group.command.mock.calls[0]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 20, transtime: 0}, {}]); @@ -734,7 +735,7 @@ describe('Publish', () => { settings.set(['devices', device.ieeeAddr, 'transition'], 20); const endpoint = device.getEndpoint(1); const payload = {brightness: 200, transition: 10}; - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command.mock.calls[0]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 200, transtime: 100}, {}]); @@ -744,7 +745,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); const payload = {'state': 'ON', 'color': {'x': 0.701, 'y': 0.299}, 'transition': 3, 'brightness': 100}; - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(2); expect(endpoint.command.mock.calls[0]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 100, transtime: 30}, {}]); @@ -754,27 +755,27 @@ describe('Publish', () => { it('Should turn device off when brightness 0 is send', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({brightness: 50, state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({brightness: 50, state: 'ON'})); await flushPromises(); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({brightness: 0})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({brightness: 0})); await flushPromises(); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(3); expect(endpoint.command.mock.calls[0]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 50, transtime: 0}, {}]); expect(endpoint.command.mock.calls[1]).toEqual(["genOnOff", "off", {}, {}]); expect(endpoint.command.mock.calls[2]).toEqual(["genOnOff", "on", {}, {}]); expect(MQTT.publish).toHaveBeenCalledTimes(3); - expect(MQTT.publish.mock.calls[0]).toEqual(["zigbee2mqtt/bulb_color", JSON.stringify({state: 'ON', brightness: 50}), {"qos": 0, "retain": false}, expect.any(Function)]); - expect(MQTT.publish.mock.calls[1]).toEqual(["zigbee2mqtt/bulb_color", JSON.stringify({state: 'OFF', brightness: 0}), {"qos": 0, "retain": false}, expect.any(Function)]); - expect(MQTT.publish.mock.calls[2]).toEqual(["zigbee2mqtt/bulb_color", JSON.stringify({state: 'ON', brightness: 50}), {"qos": 0, "retain": false}, expect.any(Function)]); + expect(MQTT.publish.mock.calls[0]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'ON', brightness: 50}), {"qos": 0, "retain": false}, expect.any(Function)]); + expect(MQTT.publish.mock.calls[1]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'OFF', brightness: 0}), {"qos": 0, "retain": false}, expect.any(Function)]); + expect(MQTT.publish.mock.calls[2]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'ON', brightness: 50}), {"qos": 0, "retain": false}, expect.any(Function)]); }); it('Should turn device off when brightness 0 is send with light_brightness converter', async () => { const device = zigbeeHerdsman.devices.HGZB04D; const endpoint = device.getEndpoint(1); const payload = {'brightness': 0}; - await MQTT.events.message('zigbee2mqtt/dimmer_wall_switch/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/dimmer_wall_switch/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command.mock.calls[0]).toEqual(["genOnOff", "off", {}, {}]); @@ -784,7 +785,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); const payload = {"color":{"hue":250, "saturation":50}}; - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command.mock.calls[0]).toEqual(["lightingColorCtrl", "enhancedMoveToHueAndSaturation", {"direction": 0, "enhancehue": 44891.475, "saturation": 199.21474, "transtime": 0,}, {}]); @@ -797,7 +798,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.ZNCLDJ11LM; const endpoint = device.getEndpoint(1); const payload = {'state': 'OPEN'}; - await MQTT.events.message('zigbee2mqtt/curtain/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/curtain/set', stringify(payload)); await flushPromises(); expect(endpoint.write).toHaveBeenCalledTimes(1); expect(endpoint.write).toHaveBeenCalledWith("genAnalogOutput", {"85": {"type": 57, "value": 100}}); @@ -807,7 +808,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.ZNCLDJ11LM; const endpoint = device.getEndpoint(1); const payload = {'position': 10}; - await MQTT.events.message('zigbee2mqtt/curtain/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/curtain/set', stringify(payload)); await flushPromises(); expect(endpoint.write).toHaveBeenCalledTimes(1); expect(endpoint.write).toHaveBeenCalledWith("genAnalogOutput", {"85": {"type": 57, "value": 10}}); @@ -817,7 +818,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.ZNCLDJ11LM; const endpoint = device.getEndpoint(1); const payload = {'state': 'CLOSE'}; - await MQTT.events.message('zigbee2mqtt/curtain/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/curtain/set', stringify(payload)); await flushPromises(); expect(endpoint.write).toHaveBeenCalledTimes(1); expect(endpoint.write).toHaveBeenCalledWith("genAnalogOutput", {"85": {"type": 57, "value": 0}}); @@ -827,7 +828,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.ZNCLDJ11LM; const endpoint = device.getEndpoint(1); const payload = {'state': 'STOP'}; - await MQTT.events.message('zigbee2mqtt/curtain/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/curtain/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("closuresWindowCovering", "stop", {}, {}); @@ -837,7 +838,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); const payload = {state: 'ON', transition: 3}; - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command.mock.calls[0]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 254, transtime: 30}, {}]); @@ -847,7 +848,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); const payload = {state: 'ON', transition: 0}; - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command.mock.calls[0]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 254, transtime: 0}, {}]); @@ -857,7 +858,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); const payload = {state: 'OFF', transition: 1}; - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command.mock.calls[0]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 0, transtime: 10}, {}]); @@ -870,58 +871,58 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); // Set initial brightness in state - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({brightness: 200})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({brightness: 200})); await flushPromises(); endpoint.command.mockClear(); MQTT.publish.mockClear(); // Turn off - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: 'OFF', transition: 3})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF', transition: 3})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command.mock.calls[0]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 0, transtime: 30}, {}]); expect(MQTT.publish).toHaveBeenCalledTimes(1); - expect(MQTT.publish.mock.calls[0]).toEqual(["zigbee2mqtt/bulb_color", JSON.stringify({state: 'OFF', brightness: 0}), {"qos": 0, "retain": false}, expect.any(Function)]); + expect(MQTT.publish.mock.calls[0]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'OFF', brightness: 0}), {"qos": 0, "retain": false}, expect.any(Function)]); // Bulb reports brightness while decreasing brightness await zigbeeHerdsman.events.message({data: {currentLevel: 1}, cluster: 'genLevelCtrl', device, endpoint, type: 'attributeReport', linkquality: 10}); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(2); - expect(MQTT.publish.mock.calls[1]).toEqual(["zigbee2mqtt/bulb_color", JSON.stringify({state: 'OFF', brightness: 1, linkquality: 10}), {"qos": 0, "retain": false}, expect.any(Function)]); + expect(MQTT.publish.mock.calls[1]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'OFF', brightness: 1, linkquality: 10}), {"qos": 0, "retain": false}, expect.any(Function)]); // Turn on again - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: 'ON', transition: 3})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON', transition: 3})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(2); expect(endpoint.command.mock.calls[1]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 200, transtime: 30}, {}]); expect(MQTT.publish).toHaveBeenCalledTimes(3); - expect(MQTT.publish.mock.calls[2]).toEqual(["zigbee2mqtt/bulb_color", JSON.stringify({state: 'ON', brightness: 200, linkquality: 10}), {"qos": 0, "retain": false}, expect.any(Function)]); + expect(MQTT.publish.mock.calls[2]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'ON', brightness: 200, linkquality: 10}), {"qos": 0, "retain": false}, expect.any(Function)]); }); it('When device is turned off with transition and turned on WITHOUT transition it should restore the brightness', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); // Set initial brightness in state - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({brightness: 200})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({brightness: 200})); await flushPromises(); endpoint.command.mockClear(); MQTT.publish.mockClear(); // Turn off - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: 'OFF', transition: 3})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF', transition: 3})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command.mock.calls[0]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 0, transtime: 30}, {}]); expect(MQTT.publish).toHaveBeenCalledTimes(1); - expect(MQTT.publish.mock.calls[0]).toEqual(["zigbee2mqtt/bulb_color", JSON.stringify({state: 'OFF', brightness: 0}), {"qos": 0, "retain": false}, expect.any(Function)]); + expect(MQTT.publish.mock.calls[0]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'OFF', brightness: 0}), {"qos": 0, "retain": false}, expect.any(Function)]); // Turn on again - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(2); expect(endpoint.command.mock.calls[1]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 200, transtime: 0}, {}]); expect(MQTT.publish).toHaveBeenCalledTimes(2); - expect(MQTT.publish.mock.calls[1]).toEqual(["zigbee2mqtt/bulb_color", JSON.stringify({state: 'ON', brightness: 200}), {"qos": 0, "retain": false}, expect.any(Function)]); + expect(MQTT.publish.mock.calls[1]).toEqual(["zigbee2mqtt/bulb_color", stringify({state: 'ON', brightness: 200}), {"qos": 0, "retain": false}, expect.any(Function)]); }); it('Home Assistant: should set state', async () => { @@ -929,7 +930,7 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); const payload = {state: 'ON'}; - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command.mock.calls[0]).toEqual(["genOnOff", "on", {}, {}]); @@ -944,7 +945,7 @@ describe('Publish', () => { controller.state.set(device.ieeeAddr, {state: 'ON'}) const endpoint = device.getEndpoint(1); const payload = {state: 'ON', color_temp: 100}; - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command.mock.calls[0]).toEqual(["lightingColorCtrl", "moveToColorTemp", {colortemp: 100, transtime: 0}, {}]); @@ -959,7 +960,7 @@ describe('Publish', () => { controller.state.set(device.ieeeAddr, {state: 'OFF'}) const endpoint = device.getEndpoint(1); const payload = {state: 'ON', color_temp: 100}; - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(2); expect(endpoint.command.mock.calls[0]).toEqual(["genOnOff", "on", {}, {}]); @@ -971,13 +972,13 @@ describe('Publish', () => { expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({state: 'ON', color: {x: 0.280632719756407, y: 0.288286029784579}, color_temp: 100}); }); - it('Home Assistant: should not set state when color is also set', async () => { + it('onlythis Home Assistant: should not set state when color is also set', async () => { settings.set(['homeassistant'], true); const device = zigbeeHerdsman.devices.bulb_color; controller.state.set(device.ieeeAddr, {state: 'ON'}) const endpoint = device.getEndpoint(1); const payload = {state: 'ON', color: {x: 0.41, y: 0.25}}; - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command.mock.calls[0]).toEqual(["lightingColorCtrl", "moveToColor", {colorx: 26869, colory: 16384, transtime: 0}, {}]); @@ -988,7 +989,7 @@ describe('Publish', () => { it('Should publish correct state on toggle command to zigbee bulb', async () => { const endpoint = zigbeeHerdsman.devices.bulb_color.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: 'TOGGLE'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'TOGGLE'})); await flushPromises(); // At this point the bulb has no state yet, so we cannot determine the next state and therefore shouldn't publish it to MQTT. @@ -997,12 +998,12 @@ describe('Publish', () => { expect(MQTT.publish).toHaveBeenCalledTimes(0); // Turn bulb off so that the bulb gets a state. - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: 'OFF'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenNthCalledWith(1, 'zigbee2mqtt/bulb_color', - JSON.stringify({state: 'OFF', brightness: 0}), + stringify({state: 'OFF', brightness: 0}), {retain: false, qos: 0}, expect.any(Function) ); @@ -1012,13 +1013,13 @@ describe('Publish', () => { return {currentLevel: 100}; } }); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: 'TOGGLE'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'TOGGLE'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(3); expect(MQTT.publish).toHaveBeenCalledTimes(2); expect(MQTT.publish).toHaveBeenNthCalledWith(2, 'zigbee2mqtt/bulb_color', - JSON.stringify({state: 'ON', brightness: 100}), + stringify({state: 'ON', brightness: 100}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -1032,13 +1033,13 @@ describe('Publish', () => { } }); controller.state.state = {[device.ieeeAddr]: {state: "OFF", brightness: 0,}}; - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: "OFF"})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: "OFF"})); await flushPromises(); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: "ON"})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: "ON"})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(2); - expect(MQTT.publish).toHaveBeenNthCalledWith(1, 'zigbee2mqtt/bulb_color', JSON.stringify({state: 'OFF', brightness: 0}), {retain: false, qos: 0}, expect.any(Function)); - expect(MQTT.publish).toHaveBeenNthCalledWith(2, 'zigbee2mqtt/bulb_color', JSON.stringify({state: 'ON', brightness: 100}), {retain: false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenNthCalledWith(1, 'zigbee2mqtt/bulb_color', stringify({state: 'OFF', brightness: 0}), {retain: false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenNthCalledWith(2, 'zigbee2mqtt/bulb_color', stringify({state: 'ON', brightness: 100}), {retain: false, qos: 0}, expect.any(Function)); expect(endpoint.command).toHaveBeenCalledTimes(2); expect(endpoint.command.mock.calls[0]).toEqual(["genOnOff", "off", {}, {}]); expect(endpoint.command.mock.calls[1]).toEqual(["genOnOff", "on", {}, {}]); @@ -1047,7 +1048,7 @@ describe('Publish', () => { it('Should publish messages with options disableDefaultResponse', async () => { const device = zigbeeHerdsman.devices.GLEDOPTO1112; const endpoint = device.getEndpoint(11); - await MQTT.events.message('zigbee2mqtt/led_controller_1/set', JSON.stringify({state: 'OFF'})); + await MQTT.events.message('zigbee2mqtt/led_controller_1/set', stringify({state: 'OFF'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("genOnOff", "off", {}, {disableDefaultResponse: true}); @@ -1060,7 +1061,7 @@ describe('Publish', () => { it('Should publish messages to zigbee devices', async () => { settings.set(['advanced', 'last_seen'], 'ISO_8601') const endpoint = zigbeeHerdsman.devices.bulb_color.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({brightness: '200'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({brightness: '200'})); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(1); expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color'); @@ -1070,7 +1071,7 @@ describe('Publish', () => { it('Should publish brightness_move up to zigbee devices', async () => { const endpoint = zigbeeHerdsman.devices.bulb_color.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({brightness_move: -40})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({brightness_move: -40})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("genLevelCtrl", "move", {"movemode": 1, "rate": 40}, {}); @@ -1078,7 +1079,7 @@ describe('Publish', () => { it('Should publish brightness_move down to zigbee devices', async () => { const endpoint = zigbeeHerdsman.devices.bulb_color.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({brightness_move: 30})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({brightness_move: 30})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("genLevelCtrl", "move", {"movemode": 0, "rate": 30}, {}); @@ -1087,7 +1088,7 @@ describe('Publish', () => { it('HS2WD-E burglar warning', async () => { const endpoint = zigbeeHerdsman.devices.HS2WD.getEndpoint(1); const payload = {warning: {duration: 100, mode: 'burglar', strobe: true, level: 'high'}}; - await MQTT.events.message('zigbee2mqtt/siren/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/siren/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("ssIasWd", "startWarning", {"startwarninginfo": 22, "warningduration": 100}, {disableDefaultResponse: true}); @@ -1096,7 +1097,7 @@ describe('Publish', () => { it('HS2WD-E emergency warning', async () => { const endpoint = zigbeeHerdsman.devices.HS2WD.getEndpoint(1); const payload = {warning: {duration: 10, mode: 'emergency', strobe: false, level: 'very_high'}}; - await MQTT.events.message('zigbee2mqtt/siren/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/siren/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("ssIasWd", "startWarning", {"startwarninginfo": 51, "warningduration": 10}, {disableDefaultResponse: true}); @@ -1105,7 +1106,7 @@ describe('Publish', () => { it('HS2WD-E emergency without level', async () => { const endpoint = zigbeeHerdsman.devices.HS2WD.getEndpoint(1); const payload = {warning: {duration: 10, mode: 'emergency', strobe: false}}; - await MQTT.events.message('zigbee2mqtt/siren/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/siren/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("ssIasWd", "startWarning", {"startwarninginfo": 49, "warningduration": 10}, {disableDefaultResponse: true}); @@ -1114,7 +1115,7 @@ describe('Publish', () => { it('HS2WD-E wrong payload (should use defaults)', async () => { const endpoint = zigbeeHerdsman.devices.HS2WD.getEndpoint(1); const payload = {warning: 'wrong'}; - await MQTT.events.message('zigbee2mqtt/siren/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/siren/set', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("ssIasWd", "startWarning", {"startwarninginfo": 53, "warningduration": 10}, {disableDefaultResponse: true}); @@ -1122,14 +1123,14 @@ describe('Publish', () => { it('Shouldnt do anythign when device is not supported', async () => { const payload = {state: 'ON'}; - await MQTT.events.message('zigbee2mqtt/unsupported2/set', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/unsupported2/set', stringify(payload)); await flushPromises(); expectNothingPublished(); }); it('Should publish state to roller shutter', async () => { const endpoint = zigbeeHerdsman.devices.roller_shutter.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/roller_shutter/set', JSON.stringify({state: 'OPEN'})); + await MQTT.events.message('zigbee2mqtt/roller_shutter/set', stringify({state: 'OPEN'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("genLevelCtrl", "moveToLevelWithOnOff", {"level": "255", "transtime": 0}, {}); @@ -1142,7 +1143,7 @@ describe('Publish', () => { it('Should publish to MKS-CM-W5', async () => { const device = zigbeeHerdsman.devices['MKS-CM-W5']; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/MKS-CM-W5/l3/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/MKS-CM-W5/l3/set', stringify({state: 'ON'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command).toHaveBeenCalledWith("manuSpecificTuyaDimmer", "setData", {data: [1,1], dp: 259, fn: 0, status: 0, transid: expect.any(Number)}, {disableDefaultResponse: true}); @@ -1157,7 +1158,7 @@ describe('Publish', () => { jest.useFakeTimers(); const device = zigbeeHerdsman.devices['GL-S-007ZS']; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/GL-S-007ZS/set', JSON.stringify({state: 'ON', brightness: 20})); + await MQTT.events.message('zigbee2mqtt/GL-S-007ZS/set', stringify({state: 'ON', brightness: 20})); await flushPromises(); jest.runAllTimers(); await flushPromises(); @@ -1165,7 +1166,7 @@ describe('Publish', () => { expect(endpoint.command.mock.calls[0]).toEqual([ 'genOnOff', 'on', {}, {} ]); expect(endpoint.command.mock.calls[1]).toEqual([ 'genLevelCtrl', 'moveToLevelWithOnOff', { level: 20, transtime: 0 }, {} ]); expect(MQTT.publish).toHaveBeenCalledTimes(1); - expect(MQTT.publish.mock.calls[0]).toEqual([ 'zigbee2mqtt/GL-S-007ZS', '{"state":"ON","brightness":20}', { qos: 0, retain: false }, expect.any(Function)]); + expect(MQTT.publish.mock.calls[0]).toEqual([ 'zigbee2mqtt/GL-S-007ZS', stringify({"state":"ON","brightness":20}), { qos: 0, retain: false }, expect.any(Function)]); jest.useRealTimers(); }); @@ -1174,7 +1175,7 @@ describe('Publish', () => { const endpoint = device.getEndpoint(1); const payload = {'brightness_move': 20}; logger.error.mockClear(); - await MQTT.events.message('zigbee2mqtt/bulb_color/get', JSON.stringify(payload)); + await MQTT.events.message('zigbee2mqtt/bulb_color/get', stringify(payload)); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(0); expect(logger.error).toHaveBeenCalledWith("No converter available for 'get' 'brightness_move' (20)"); @@ -1186,22 +1187,22 @@ describe('Publish', () => { const endpoint = device.getEndpoint(1); endpoint.command.mockClear(); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(1); expect(endpoint.command.mock.calls[0]).toEqual(["genOnOff", "on", {}, {}]); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({"state": "ON", "brightness": 123})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({"state": "ON", "brightness": 123})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(2); expect(endpoint.command.mock.calls[1]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 123, transtime: 0}, {}]); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({"state": "OFF"})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({"state": "OFF"})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(3); expect(endpoint.command.mock.calls[2]).toEqual(["genOnOff", "off", {}, {}]); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({"state": "ON", "transition": 1.0})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({"state": "ON", "transition": 1.0})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(4); expect(endpoint.command.mock.calls[3]).toEqual(["genLevelCtrl", "moveToLevelWithOnOff", {level: 123, transtime: 10}, {}]); @@ -1212,13 +1213,13 @@ describe('Publish', () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({'state': 'ON'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({'state': 'ON'})); await flushPromises(); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({'brightness': 150})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({'brightness': 150})); await flushPromises(); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({'state': 'OFF'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({'state': 'OFF'})); await flushPromises(); - await MQTT.events.message('zigbee2mqtt/bulb_color/set', JSON.stringify({'state': 'ON'})); + await MQTT.events.message('zigbee2mqtt/bulb_color/set', stringify({'state': 'ON'})); await flushPromises(); expect(endpoint.command).toHaveBeenCalledTimes(4); @@ -1228,9 +1229,9 @@ describe('Publish', () => { expect(endpoint.command.mock.calls[3]).toEqual(["genOnOff", "on", {}, {}]); expect(MQTT.publish).toHaveBeenCalledTimes(4); - expect(MQTT.publish.mock.calls[0]).toEqual([ 'zigbee2mqtt/bulb_color', '{"state":"ON"}', { qos: 0, retain: false }, expect.any(Function)]); - expect(MQTT.publish.mock.calls[1]).toEqual([ 'zigbee2mqtt/bulb_color', '{"state":"ON","brightness":150}', { qos: 0, retain: false }, expect.any(Function)]); - expect(MQTT.publish.mock.calls[2]).toEqual([ 'zigbee2mqtt/bulb_color', '{"state":"OFF","brightness":0}', { qos: 0, retain: false }, expect.any(Function)]); - expect(MQTT.publish.mock.calls[3]).toEqual([ 'zigbee2mqtt/bulb_color', '{"state":"ON","brightness":150}', { qos: 0, retain: false }, expect.any(Function)]); + expect(MQTT.publish.mock.calls[0]).toEqual([ 'zigbee2mqtt/bulb_color', stringify({"state":"ON"}), { qos: 0, retain: false }, expect.any(Function)]); + expect(MQTT.publish.mock.calls[1]).toEqual([ 'zigbee2mqtt/bulb_color', stringify({"state":"ON","brightness":150}), { qos: 0, retain: false }, expect.any(Function)]); + expect(MQTT.publish.mock.calls[2]).toEqual([ 'zigbee2mqtt/bulb_color', stringify({"state":"OFF","brightness":0}), { qos: 0, retain: false }, expect.any(Function)]); + expect(MQTT.publish.mock.calls[3]).toEqual([ 'zigbee2mqtt/bulb_color', stringify({"state":"ON","brightness":150}), { qos: 0, retain: false }, expect.any(Function)]); }); }); diff --git a/test/receive.test.js b/test/receive.test.js index 561e1c6e..7b0f3c4b 100755 --- a/test/receive.test.js +++ b/test/receive.test.js @@ -1,5 +1,6 @@ const data = require('./stub/data'); const logger = require('./stub/logger'); +const stringify = require('json-stable-stringify'); const zigbeeHerdsman = require('./stub/zigbeeHerdsman'); const MQTT = require('./stub/mqtt'); const settings = require('../lib/util/settings'); @@ -28,7 +29,7 @@ describe('Receive', () => { await zigbeeHerdsman.events.message(payload); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(1); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/button', JSON.stringify({action: 'single', click: 'single', linkquality: 10}), {retain: false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/button', stringify({action: 'single', click: 'single', linkquality: 10}), {retain: false, qos: 0}, expect.any(Function)); }); it('Should handle a zigbee message which uses ep (left)', async () => { @@ -74,7 +75,7 @@ describe('Receive', () => { await zigbeeHerdsman.events.message({data: {currentPositionLiftPercentage: 90, currentPositionTiltPercentage: 80}, cluster: 'closuresWindowCovering', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10}); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(1); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/J1_cover', JSON.stringify({position: 10, tilt: 20, linkquality: 10}), {retain: false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/J1_cover', stringify({position: 10, tilt: 20, linkquality: 10}), {retain: false, qos: 0}, expect.any(Function)); // Inverted MQTT.publish.mockClear(); @@ -82,7 +83,7 @@ describe('Receive', () => { await zigbeeHerdsman.events.message({data: {currentPositionLiftPercentage: 90, currentPositionTiltPercentage: 80}, cluster: 'closuresWindowCovering', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10}); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(1); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/J1_cover', JSON.stringify({position: 90, tilt: 80, linkquality: 10}), {retain: false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/J1_cover', stringify({position: 90, tilt: 80, linkquality: 10}), {retain: false, qos: 0}, expect.any(Function)); }); it('Should allow to disable the legacy integration', async () => { @@ -93,7 +94,7 @@ describe('Receive', () => { await zigbeeHerdsman.events.message(payload); await flushPromises(); expect(MQTT.publish).toHaveBeenCalledTimes(1); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/button', JSON.stringify({action: 'single', linkquality: 10}), {retain: false, qos: 0}, expect.any(Function)); + expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/button', stringify({action: 'single', linkquality: 10}), {retain: false, qos: 0}, expect.any(Function)); }); it('Should debounce messages', async () => { @@ -175,7 +176,7 @@ describe('Receive', () => { const device = zigbeeHerdsman.devices.bulb; settings.set(['devices', device.ieeeAddr, 'debounce'], 0.1); await zigbeeHerdsman.events.message({data: {onOff: 0}, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10}); - await MQTT.events.message('zigbee2mqtt/bulb/set', JSON.stringify({state: 'ON'})); + await MQTT.events.message('zigbee2mqtt/bulb/set', stringify({state: 'ON'})); await flushPromises(); jest.runAllTimers(); expect(MQTT.publish).toHaveBeenCalledTimes(2); diff --git a/test/stub/data.js b/test/stub/data.js index ce5ff9b0..b324aea6 100644 --- a/test/stub/data.js +++ b/test/stub/data.js @@ -2,6 +2,7 @@ const tmp = require('tmp'); const yaml = require('../../lib/util/yaml'); const path = require('path'); const fs = require('fs'); +const stringify = require('json-stable-stringify'); const mockDir = tmp.dirSync().name; const mockDirStorage = tmp.dirSync().name; @@ -186,7 +187,7 @@ function writeDefaultConfiguration() { } function writeEmptyState() { - fs.writeFileSync(stateFile, JSON.stringify({})); + fs.writeFileSync(stateFile, stringify({})); } function removeState() { @@ -212,7 +213,7 @@ function writeDefaultState() { }, } - fs.writeFileSync(path.join(mockDir, 'state.json'), JSON.stringify(state)); + fs.writeFileSync(path.join(mockDir, 'state.json'), stringify(state)); } jest.mock('../../lib/util/data', () => ({