feat: Use Vitest for testing (#25284)

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
This commit is contained in:
Nerivec
2024-12-22 20:19:45 +01:00
committed by GitHub
co-authored by Koen Kanters
parent 5d059b7bcb
commit 06f44533af
51 changed files with 2606 additions and 4771 deletions
+37 -37
View File
@@ -1,6 +1,6 @@
import * as data from '../mocks/data';
import {mockLogger} from '../mocks/logger';
import {mockMQTT, events as mockMQTTEvents} from '../mocks/mqtt';
import {events as mockMQTTEvents, mockMQTTPublishAsync} from '../mocks/mqtt';
import {flushPromises} from '../mocks/utils';
import {devices, events as mockZHEvents, returnDevices} from '../mocks/zigbeeHerdsman';
@@ -13,7 +13,7 @@ import Availability from '../../lib/extension/availability';
import * as settings from '../../lib/util/settings';
import utils from '../../lib/util/utils';
const mocksClear = [mockMQTT.publishAsync, mockLogger.warning, mockLogger.info];
const mocksClear = [mockMQTTPublishAsync, mockLogger.warning, mockLogger.info];
returnDevices.push(
devices.bulb_color.ieeeAddr,
@@ -37,22 +37,22 @@ describe('Extension: Availability', () => {
};
const setTimeAndAdvanceTimers = async (value: number): Promise<void> => {
jest.setSystemTime(Date.now() + value);
await jest.advanceTimersByTimeAsync(value);
vi.setSystemTime(Date.now() + value);
await vi.advanceTimersByTimeAsync(value);
};
beforeAll(async () => {
jest.spyOn(utils, 'sleep').mockImplementation();
jest.useFakeTimers();
vi.spyOn(utils, 'sleep').mockImplementation(vi.fn());
vi.useFakeTimers();
settings.reRead();
settings.set(['availability'], {enabled: true});
controller = new Controller(jest.fn(), jest.fn());
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
await flushPromises();
});
beforeEach(async () => {
jest.setSystemTime(utils.minutes(1));
vi.setSystemTime(utils.minutes(1));
data.writeDefaultConfiguration();
settings.reRead();
settings.set(['availability'], {enabled: true});
@@ -67,16 +67,16 @@ describe('Extension: Availability', () => {
afterAll(async () => {
await controller.stop();
jest.useRealTimers();
vi.useRealTimers();
});
it('Should publish availability on startup for device where it is enabled for', async () => {
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color/availability', stringify({state: 'online'}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color/availability', stringify({state: 'online'}), {
retain: true,
qos: 1,
});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/remote/availability', stringify({state: 'online'}), {retain: true, qos: 1});
expect(mockMQTT.publishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bulb_color_2/availability', stringify({state: 'online'}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/remote/availability', stringify({state: 'online'}), {retain: true, qos: 1});
expect(mockMQTTPublishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bulb_color_2/availability', stringify({state: 'online'}), {
retain: true,
qos: 1,
});
@@ -106,14 +106,14 @@ describe('Extension: Availability', () => {
});
it('Should publish offline for active device when not seen for 10 minutes', async () => {
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await setTimeAndAdvanceTimers(utils.minutes(5));
expect(devices.bulb_color.ping).toHaveBeenCalledTimes(0);
await setTimeAndAdvanceTimers(utils.minutes(7));
expect(devices.bulb_color.ping).toHaveBeenCalledTimes(1);
expect(devices.bulb_color.ping).toHaveBeenNthCalledWith(1, true);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color/availability', stringify({state: 'offline'}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color/availability', stringify({state: 'offline'}), {
retain: true,
qos: 1,
});
@@ -127,20 +127,20 @@ describe('Extension: Availability', () => {
});
it('Should publish offline for passive device when not seen for 25 hours', async () => {
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await setTimeAndAdvanceTimers(utils.hours(26));
expect(devices.remote.ping).toHaveBeenCalledTimes(0);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/remote/availability', stringify({state: 'offline'}), {retain: true, qos: 1});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/remote/availability', stringify({state: 'offline'}), {retain: true, qos: 1});
});
it('Should reset ping timer when device last seen changes for active device', async () => {
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await setTimeAndAdvanceTimers(utils.minutes(5));
expect(devices.bulb_color.ping).toHaveBeenCalledTimes(0);
await mockZHEvents.lastSeenChanged({device: devices.bulb_color});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color/availability', stringify({state: 'offline'}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color/availability', stringify({state: 'offline'}), {
retain: true,
qos: 1,
});
@@ -154,7 +154,7 @@ describe('Extension: Availability', () => {
});
it('Should ping again when first ping fails', async () => {
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockZHEvents.lastSeenChanged({device: devices.bulb_color});
@@ -169,13 +169,13 @@ describe('Extension: Availability', () => {
});
it('Should reset ping timer when device last seen changes for passive device', async () => {
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await setTimeAndAdvanceTimers(utils.hours(24));
expect(devices.remote.ping).toHaveBeenCalledTimes(0);
await mockZHEvents.lastSeenChanged({device: devices.remote});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/remote/availability', stringify({state: 'offline'}), {retain: true, qos: 1});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/remote/availability', stringify({state: 'offline'}), {retain: true, qos: 1});
await setTimeAndAdvanceTimers(utils.hours(25));
expect(devices.remote.ping).toHaveBeenCalledTimes(0);
@@ -185,10 +185,10 @@ describe('Extension: Availability', () => {
});
it('Should immediately mark device as online when it lastSeen changes', async () => {
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await setTimeAndAdvanceTimers(utils.minutes(15));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color/availability', stringify({state: 'offline'}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color/availability', stringify({state: 'offline'}), {
retain: true,
qos: 1,
});
@@ -196,7 +196,7 @@ describe('Extension: Availability', () => {
devices.bulb_color.lastSeen = Date.now();
await mockZHEvents.lastSeenChanged({device: devices.bulb_color});
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color/availability', stringify({state: 'online'}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color/availability', stringify({state: 'online'}), {
retain: true,
qos: 1,
});
@@ -310,18 +310,18 @@ describe('Extension: Availability', () => {
});
it('Should republish availability when device is renamed', async () => {
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/rename', stringify({from: 'bulb_color', to: 'bulb_new_name'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color/availability', '', {retain: true, qos: 1});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_new_name/availability', stringify({state: 'online'}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color/availability', '', {retain: true, qos: 1});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_new_name/availability', stringify({state: 'online'}), {
retain: true,
qos: 1,
});
await setTimeAndAdvanceTimers(utils.hours(12));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_new_name/availability', stringify({state: 'offline'}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_new_name/availability', stringify({state: 'offline'}), {
retain: true,
qos: 1,
});
@@ -330,10 +330,10 @@ describe('Extension: Availability', () => {
it('Should publish availability payload in JSON format', async () => {
await resetExtension();
devices.remote.ping.mockClear();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await setTimeAndAdvanceTimers(utils.hours(26));
expect(devices.remote.ping).toHaveBeenCalledTimes(0);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/remote/availability', stringify({state: 'offline'}), {retain: true, qos: 1});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/remote/availability', stringify({state: 'offline'}), {retain: true, qos: 1});
});
it('Should publish availability for groups', async () => {
@@ -341,21 +341,21 @@ describe('Extension: Availability', () => {
await resetExtension();
devices.bulb_color_2.ping.mockClear();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_tradfri_remote/availability', stringify({state: 'online'}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_tradfri_remote/availability', stringify({state: 'online'}), {
retain: true,
qos: 1,
});
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await setTimeAndAdvanceTimers(utils.minutes(12));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_tradfri_remote/availability', stringify({state: 'offline'}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_tradfri_remote/availability', stringify({state: 'offline'}), {
retain: true,
qos: 1,
});
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
devices.bulb_color_2.lastSeen = Date.now();
await mockZHEvents.lastSeenChanged({device: devices.bulb_color_2});
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_tradfri_remote/availability', stringify({state: 'online'}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_tradfri_remote/availability', stringify({state: 'online'}), {
retain: true,
qos: 1,
});
@@ -365,7 +365,7 @@ describe('Extension: Availability', () => {
// @ts-expect-error private
const availability = controller.extensions.find((extension) => extension instanceof Availability)!;
// @ts-expect-error private
const publishAvailabilitySpy = jest.spyOn(availability, 'publishAvailability');
const publishAvailabilitySpy = vi.spyOn(availability, 'publishAvailability');
devices.bulb_color.ping.mockImplementationOnce(() => new Promise((resolve) => setTimeout(resolve, 1000)));
// @ts-expect-error private
@@ -380,7 +380,7 @@ describe('Extension: Availability', () => {
expect(availability.pingQueue).toEqual([]);
// Validate the stop-interrupt implicitly by checking that it prevents further function invocations
expect(publishAvailabilitySpy).not.toHaveBeenCalled();
devices.bulb_color.ping = jest.fn(); // ensure reset
devices.bulb_color.ping = vi.fn(); // ensure reset
});
it('Should prevent instance restart', async () => {
+31 -31
View File
@@ -1,7 +1,7 @@
import * as data from '../mocks/data';
import {mockDebounce} from '../mocks/debounce';
import {mockLogger} from '../mocks/logger';
import {mockMQTT, events as mockMQTTEvents} from '../mocks/mqtt';
import {events as mockMQTTEvents, mockMQTTPublishAsync} from '../mocks/mqtt';
import {flushPromises} from '../mocks/utils';
import {Device, devices, groups, events as mockZHEvents} from '../mocks/zigbeeHerdsman';
@@ -12,7 +12,7 @@ import * as settings from '../../lib/util/settings';
const mocksClear = [
mockDebounce,
mockMQTT.publishAsync,
mockMQTTPublishAsync,
devices.bulb_color.getEndpoint(1)!.configureReporting,
devices.bulb_color.getEndpoint(1)!.bind,
devices.bulb_color_2.getEndpoint(1)!.read,
@@ -31,15 +31,15 @@ describe('Extension: Bind', () => {
endpoint.read.mockClear();
endpoint.write.mockClear();
endpoint.configureReporting.mockClear();
endpoint.bind = jest.fn();
endpoint.bind = vi.fn();
endpoint.bind.mockClear();
endpoint.unbind.mockClear();
}
};
beforeAll(async () => {
jest.useFakeTimers();
controller = new Controller(jest.fn(), jest.fn());
vi.useFakeTimers();
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
await flushPromises();
});
@@ -53,7 +53,7 @@ describe('Extension: Bind', () => {
});
afterAll(async () => {
jest.useRealTimers();
vi.useRealTimers();
});
it('Should bind to device and configure reporting', async () => {
@@ -92,7 +92,7 @@ describe('Extension: Bind', () => {
{attribute: 'currentX', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1},
{attribute: 'currentY', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1},
]);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({
transaction: '1234',
@@ -108,7 +108,7 @@ describe('Extension: Bind', () => {
{retain: false, qos: 0},
);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0});
// Teardown
target.binds = originalTargetBinds;
@@ -116,10 +116,10 @@ describe('Extension: Bind', () => {
});
it('Should throw error on invalid payload', async () => {
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/bind', stringify({fromz: 'remote', to: 'bulb_color'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({data: {}, status: 'error', error: 'Invalid payload'}),
{retain: false, qos: 0},
@@ -168,7 +168,7 @@ describe('Extension: Bind', () => {
{attribute: 'currentX', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1},
{attribute: 'currentY', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1},
]);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({
transaction: '1234',
@@ -184,7 +184,7 @@ describe('Extension: Bind', () => {
{retain: false, qos: 0},
);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0});
// Teardown
target.binds = originalTargetBinds;
@@ -238,7 +238,7 @@ describe('Extension: Bind', () => {
{attribute: 'currentX', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1},
{attribute: 'currentY', minimumReportInterval: 5, maximumReportInterval: 3600, reportableChange: 1},
]);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({
transaction: '1234',
@@ -254,7 +254,7 @@ describe('Extension: Bind', () => {
{retain: false, qos: 0},
);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0});
// Teardown
target.configuredReportings = originalTargetCR;
@@ -271,7 +271,7 @@ describe('Extension: Bind', () => {
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(1);
expect(endpoint.bind).toHaveBeenCalledWith('genOnOff', target);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({data: {from: 'remote', from_endpoint: 'default', to: 'bulb_color', clusters: ['genOnOff'], failed: []}, status: 'ok'}),
{retain: false, qos: 0},
@@ -286,7 +286,7 @@ describe('Extension: Bind', () => {
mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'button'}));
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(0);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({data: {}, status: 'error', error: 'Nothing to bind'}),
{retain: false, qos: 0},
@@ -335,7 +335,7 @@ describe('Extension: Bind', () => {
{attribute: 'currentY', minimumReportInterval: 5, maximumReportInterval: 0xffff, reportableChange: 1},
]);
expect(devices.bulb_color.meta.configured).toBe(332242049);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/unbind',
stringify({
data: {from: 'remote', from_endpoint: 'default', to: 'bulb_color', clusters: ['genScenes', 'genOnOff', 'genLevelCtrl'], failed: []},
@@ -361,7 +361,7 @@ describe('Extension: Bind', () => {
expect(endpoint.unbind).toHaveBeenCalledWith('genOnOff', target);
expect(endpoint.unbind).toHaveBeenCalledWith('genLevelCtrl', target);
expect(endpoint.unbind).toHaveBeenCalledWith('genScenes', target);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/unbind',
stringify({
data: {from: 'remote', from_endpoint: 'default', to: 'Coordinator', clusters: ['genScenes', 'genOnOff', 'genLevelCtrl'], failed: []},
@@ -392,7 +392,7 @@ describe('Extension: Bind', () => {
expect(target1Member.configureReporting).toHaveBeenCalledWith('genLevelCtrl', [
{attribute: 'currentLevel', maximumReportInterval: 3600, minimumReportInterval: 5, reportableChange: 1},
]);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({
data: {from: 'remote', from_endpoint: 'default', to: 'group_1', clusters: ['genScenes', 'genOnOff', 'genLevelCtrl'], failed: []},
@@ -428,7 +428,7 @@ describe('Extension: Bind', () => {
expect(endpoint.unbind).toHaveBeenCalledWith('genOnOff', target);
expect(endpoint.unbind).toHaveBeenCalledWith('genLevelCtrl', target);
expect(endpoint.unbind).toHaveBeenCalledWith('genScenes', target);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/unbind',
stringify({
data: {from: 'remote', from_endpoint: 'default', to: 'group_1', clusters: ['genScenes', 'genOnOff', 'genLevelCtrl'], failed: []},
@@ -505,7 +505,7 @@ describe('Extension: Bind', () => {
expect(endpoint.bind).toHaveBeenCalledWith('genOnOff', target);
expect(endpoint.bind).toHaveBeenCalledWith('genLevelCtrl', target);
expect(endpoint.bind).toHaveBeenCalledWith('genScenes', target);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({
data: {from: 'remote', from_endpoint: 'default', to: '1', clusters: ['genScenes', 'genOnOff', 'genLevelCtrl'], failed: []},
@@ -526,7 +526,7 @@ describe('Extension: Bind', () => {
mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'bulb_color'}));
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(3);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({data: {}, status: 'error', error: 'Failed to bind'}),
{retain: false, qos: 0},
@@ -545,7 +545,7 @@ describe('Extension: Bind', () => {
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(1);
expect(endpoint.bind).toHaveBeenCalledWith('genOnOff', target);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({
data: {from: 'remote', from_endpoint: 'ep2', to: 'wall_switch_double', to_endpoint: 'right', clusters: ['genOnOff'], failed: []},
@@ -567,7 +567,7 @@ describe('Extension: Bind', () => {
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(1);
expect(endpoint.bind).toHaveBeenCalledWith('genOnOff', target);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({
data: {from: 'remote', from_endpoint: 2, to: 'wall_switch_double', to_endpoint: 3, clusters: ['genOnOff'], failed: []},
@@ -586,7 +586,7 @@ describe('Extension: Bind', () => {
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(1);
expect(endpoint.bind).toHaveBeenCalledWith('msTemperatureMeasurement', target);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({
data: {
@@ -611,7 +611,7 @@ describe('Extension: Bind', () => {
await flushPromises();
expect(endpoint.bind).toHaveBeenCalledTimes(1);
expect(endpoint.bind).toHaveBeenCalledWith('genOnOff', target);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({data: {from: 'remote', from_endpoint: 'ep2', to: 'wall_switch', clusters: ['genOnOff'], failed: []}, status: 'ok'}),
{retain: false, qos: 0},
@@ -629,7 +629,7 @@ describe('Extension: Bind', () => {
expect(endpoint.unbind).toHaveBeenCalledWith('genOnOff', 901);
expect(endpoint.unbind).toHaveBeenCalledWith('genLevelCtrl', 901);
expect(endpoint.unbind).toHaveBeenCalledWith('genScenes', 901);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/unbind',
stringify({
data: {
@@ -650,7 +650,7 @@ describe('Extension: Bind', () => {
mockClear(device);
mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote_not_existing', to: 'bulb_color'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({data: {}, status: 'error', error: "Source device 'remote_not_existing' does not exist"}),
{retain: false, qos: 0},
@@ -665,7 +665,7 @@ describe('Extension: Bind', () => {
stringify({from: 'remote', from_endpoint: 'not_existing_endpoint', to: 'bulb_color'}),
);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({data: {}, status: 'error', error: "Source device 'remote' does not have endpoint 'not_existing_endpoint'"}),
{retain: false, qos: 0},
@@ -677,7 +677,7 @@ describe('Extension: Bind', () => {
mockClear(device);
mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/bind', stringify({from: 'remote', to: 'bulb_color_not_existing'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({data: {}, status: 'error', error: "Target device or group 'bulb_color_not_existing' does not exist"}),
{retain: false, qos: 0},
@@ -692,7 +692,7 @@ describe('Extension: Bind', () => {
stringify({from: 'remote', to: 'bulb_color', to_endpoint: 'not_existing_endpoint'}),
);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/bind',
stringify({data: {}, status: 'error', error: "Target device 'bulb_color' does not have endpoint 'not_existing_endpoint'"}),
{retain: false, qos: 0},
File diff suppressed because it is too large Load Diff
+12 -12
View File
@@ -1,6 +1,6 @@
import * as data from '../mocks/data';
import {mockLogger} from '../mocks/logger';
import {mockMQTT, events as mockMQTTEvents} from '../mocks/mqtt';
import {events as mockMQTTEvents, mockMQTTPublishAsync} from '../mocks/mqtt';
import {flushPromises} from '../mocks/utils';
import {Device, devices, Endpoint, events as mockZHEvents} from '../mocks/zigbeeHerdsman';
@@ -9,7 +9,7 @@ import stringify from 'json-stable-stringify-without-jsonify';
import {Controller} from '../../lib/controller';
import * as settings from '../../lib/util/settings';
const mocksClear = [mockMQTT.publishAsync, mockLogger.warning, mockLogger.debug];
const mocksClear = [mockMQTTPublishAsync, mockLogger.warning, mockLogger.debug];
describe('Extension: Configure', () => {
let controller: Controller;
@@ -65,10 +65,10 @@ describe('Extension: Configure', () => {
const wait = async (ms: number): Promise<void> => await new Promise((resolve) => setTimeout(resolve, ms));
beforeAll(async () => {
jest.useFakeTimers();
controller = new Controller(jest.fn(), jest.fn());
vi.useFakeTimers();
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
await jest.runOnlyPendingTimersAsync();
await vi.runOnlyPendingTimersAsync();
});
beforeEach(async () => {
@@ -77,11 +77,11 @@ describe('Extension: Configure', () => {
mocksClear.forEach((m) => m.mockClear());
coordinatorEndpoint = devices.coordinator.getEndpoint(1)!;
await resetExtension();
await jest.runOnlyPendingTimersAsync();
await vi.runOnlyPendingTimersAsync();
});
afterAll(async () => {
jest.useRealTimers();
vi.useRealTimers();
});
it('Should configure Router on startup', async () => {
@@ -150,7 +150,7 @@ describe('Extension: Configure', () => {
await mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/configure', 'remote');
await flushPromises();
expectRemoteConfigured();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/configure',
stringify({data: {id: 'remote'}, status: 'ok'}),
{retain: false, qos: 0},
@@ -160,7 +160,7 @@ describe('Extension: Configure', () => {
it('Fail to configure via MQTT when device does not exist', async () => {
await mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/configure', stringify({id: 'not_existing_device'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/configure',
stringify({data: {}, status: 'error', error: "Device 'not_existing_device' does not exist"}),
{retain: false, qos: 0},
@@ -173,7 +173,7 @@ describe('Extension: Configure', () => {
});
await mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/configure', stringify({id: 'remote'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/configure',
stringify({data: {}, status: 'error', error: 'Failed to configure (Bind timeout after 10s)'}),
{retain: false, qos: 0},
@@ -183,7 +183,7 @@ describe('Extension: Configure', () => {
it('Fail to configure via MQTT when device has no configure', async () => {
await mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/configure', stringify({id: '0x0017882104a44559', transaction: 20}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/configure',
stringify({data: {}, status: 'error', error: "Device 'TS0601_thermostat' cannot be configured", transaction: 20}),
{retain: false, qos: 0},
@@ -193,7 +193,7 @@ describe('Extension: Configure', () => {
it('Handles invalid payload for configure via MQTT', async () => {
await mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/configure', stringify({idx: '0x0017882104a44559'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/configure',
stringify({data: {}, status: 'error', error: 'Invalid payload'}),
{retain: false, qos: 0},
+166 -154
View File
@@ -1,6 +1,6 @@
import * as data from '../mocks/data';
import {mockLogger} from '../mocks/logger';
import {mockMQTT, events as mockMQTTEvents} from '../mocks/mqtt';
import {mockMQTTEndAsync, events as mockMQTTEvents, mockMQTTPublishAsync} from '../mocks/mqtt';
import {flushPromises} from '../mocks/utils';
import {devices, mockController as mockZHController, returnDevices} from '../mocks/zigbeeHerdsman';
@@ -22,18 +22,18 @@ describe('Extension: ExternalConverters', () => {
const mockBasePath = path.join(data.mockDir, BASE_DIR);
let controller: Controller;
const existsSyncSpy = jest.spyOn(fs, 'existsSync');
const readdirSyncSpy = jest.spyOn(fs, 'readdirSync');
const mkdirSyncSpy = jest.spyOn(fs, 'mkdirSync');
const rmSyncSpy = jest.spyOn(fs, 'rmSync');
const writeFileSyncSpy = jest.spyOn(fs, 'writeFileSync');
const existsSyncSpy = vi.spyOn(fs, 'existsSync');
const readdirSyncSpy = vi.spyOn(fs, 'readdirSync');
const mkdirSyncSpy = vi.spyOn(fs, 'mkdirSync');
const rmSyncSpy = vi.spyOn(fs, 'rmSync');
const writeFileSyncSpy = vi.spyOn(fs, 'writeFileSync');
const zhcAddDefinitionSpy = jest.spyOn(zhc, 'addDefinition');
const zhcRemoveExternalDefinitionsSpy = jest.spyOn(zhc, 'removeExternalDefinitions');
const zhcAddDefinitionSpy = vi.spyOn(zhc, 'addDefinition');
const zhcRemoveExternalDefinitionsSpy = vi.spyOn(zhc, 'removeExternalDefinitions');
const mocksClear = [
mockMQTT.endAsync,
mockMQTT.publishAsync,
mockMQTTEndAsync,
mockMQTTPublishAsync,
mockLogger.debug,
mockLogger.error,
mockZHController.stop,
@@ -60,12 +60,21 @@ describe('Extension: ExternalConverters', () => {
return controller.zigbee.resolveEntity(zhDevice)! as Device;
};
const resetExtension = async (): Promise<void> => {
await controller.enableDisableExtension(false, 'ExternalConverters');
await controller.enableDisableExtension(true, 'ExternalConverters');
};
beforeAll(async () => {
jest.useFakeTimers();
vi.useFakeTimers();
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
await flushPromises();
});
afterAll(async () => {
jest.useRealTimers();
vi.useRealTimers();
});
beforeEach(async () => {
@@ -75,8 +84,6 @@ describe('Extension: ExternalConverters', () => {
data.writeDefaultState();
settings.reRead();
returnDevices.push(devices.external_converter_device.ieeeAddr, devices.coordinator.ieeeAddr);
controller = new Controller(jest.fn(), jest.fn());
});
afterEach(async () => {
@@ -85,143 +92,153 @@ describe('Extension: ExternalConverters', () => {
await controller?.stop();
});
it('loads nothing from folder', async () => {
await controller.start();
await flushPromises();
expect(existsSyncSpy).toHaveBeenCalledWith(mockBasePath);
expect(readdirSyncSpy).not.toHaveBeenCalledWith(mockBasePath);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/converters', stringify([]), {retain: true, qos: 0});
});
it('loads from folder', async () => {
useAssets();
await controller.start();
await flushPromises();
expect(getZ2MDevice(devices.external_converter_device).definition).toMatchObject({
description: 'external',
model: 'external_converter_device',
vendor: 'external',
zigbeeModel: ['external_converter_device'],
describe('from folder', () => {
beforeEach(async () => {
controller = new Controller(vi.fn(), vi.fn());
});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/converters',
stringify([
{name: 'mock-external-converter-multiple.js', code: getFileCode('mock-external-converter-multiple.js')},
{name: 'mock-external-converter.js', code: getFileCode('mock-external-converter.js')},
]),
{retain: true, qos: 0},
);
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenCalledTimes(2);
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenNthCalledWith(1, 'mock-external-converter-multiple.js');
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenNthCalledWith(2, 'mock-external-converter.js');
expect(zhcAddDefinitionSpy).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
mock: 1,
model: 'external_converters_device_1',
zigbeeModel: ['external_converter_device_1'],
vendor: 'external_1',
description: 'external_1',
}),
);
expect(zhcAddDefinitionSpy).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
mock: 2,
model: 'external_converters_device_2',
zigbeeModel: ['external_converter_device_2'],
vendor: 'external_2',
description: 'external_2',
}),
);
expect(zhcAddDefinitionSpy).toHaveBeenNthCalledWith(
3,
expect.objectContaining({
mock: true,
zigbeeModel: ['external_converter_device'],
vendor: 'external',
model: 'external_converter_device',
description: 'external',
}),
);
const bridgeDevices = mockMQTT.publishAsync.mock.calls.filter((c) => c[0] === 'zigbee2mqtt/bridge/devices');
expect(bridgeDevices.length).toBe(1);
expect(JSON.parse(bridgeDevices[0][1])).toEqual(
expect.arrayContaining([
it('loads nothing', async () => {
await controller.start();
await flushPromises();
expect(existsSyncSpy).toHaveBeenCalledWith(mockBasePath);
expect(readdirSyncSpy).not.toHaveBeenCalledWith(mockBasePath);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/converters', stringify([]), {retain: true, qos: 0});
});
it('loads converters', async () => {
useAssets();
await controller.start();
await flushPromises();
expect(getZ2MDevice(devices.external_converter_device).definition).toMatchObject({
description: 'external',
model: 'external_converter_device',
vendor: 'external',
zigbeeModel: ['external_converter_device'],
});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/converters',
stringify([
{name: 'mock-external-converter-multiple.js', code: getFileCode('mock-external-converter-multiple.js')},
{name: 'mock-external-converter.js', code: getFileCode('mock-external-converter.js')},
]),
{retain: true, qos: 0},
);
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenCalledTimes(2);
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenNthCalledWith(1, 'mock-external-converter-multiple.js');
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenNthCalledWith(2, 'mock-external-converter.js');
expect(zhcAddDefinitionSpy).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
model_id: 'external_converter_device',
supported: true,
definition: expect.objectContaining({
description: 'external',
model: 'external_converter_device',
}),
mock: 1,
model: 'external_converters_device_1',
zigbeeModel: ['external_converter_device_1'],
vendor: 'external_1',
description: 'external_1',
}),
]),
);
});
);
expect(zhcAddDefinitionSpy).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
mock: 2,
model: 'external_converters_device_2',
zigbeeModel: ['external_converter_device_2'],
vendor: 'external_2',
description: 'external_2',
}),
);
expect(zhcAddDefinitionSpy).toHaveBeenNthCalledWith(
3,
expect.objectContaining({
mock: true,
zigbeeModel: ['external_converter_device'],
vendor: 'external',
model: 'external_converter_device',
description: 'external',
}),
);
it('saves and removes from MQTT', async () => {
const converterName = 'foo.js';
const converterCode = getFileCode('mock-external-converter.js');
const converterFilePath = path.join(mockBasePath, converterName);
await controller.start();
await flushPromises();
mocksClear.forEach((m) => m.mockClear());
expect(getZ2MDevice(devices.external_converter_device).definition).toMatchObject({
description: 'Automatically generated definition',
model: 'external_converter_device',
vendor: '',
zigbeeModel: ['external_converter_device'],
const bridgeDevices = mockMQTTPublishAsync.mock.calls.filter((c) => c[0] === 'zigbee2mqtt/bridge/devices');
expect(bridgeDevices.length).toBe(1);
expect(JSON.parse(bridgeDevices[0][1])).toEqual(
expect.arrayContaining([
expect.objectContaining({
model_id: 'external_converter_device',
supported: true,
definition: expect.objectContaining({
description: 'external',
model: 'external_converter_device',
}),
}),
]),
);
});
//-- SAVE
mockMQTTEvents.message('zigbee2mqtt/bridge/request/converter/save', stringify({name: converterName, code: converterCode}));
await flushPromises();
it('saves and removes from MQTT', async () => {
const converterName = 'foo.js';
const converterCode = getFileCode('mock-external-converter.js');
const converterFilePath = path.join(mockBasePath, converterName);
expect(getZ2MDevice(devices.external_converter_device).definition).toMatchObject({
description: 'external',
model: 'external_converter_device',
vendor: 'external',
zigbeeModel: ['external_converter_device'],
});
expect(mkdirSyncSpy).toHaveBeenCalledWith(mockBasePath, {recursive: true});
expect(writeFileSyncSpy).toHaveBeenCalledWith(converterFilePath, converterCode, 'utf8');
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenCalledTimes(1);
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenNthCalledWith(1, converterName);
expect(zhcAddDefinitionSpy).toHaveBeenCalledWith(
expect.objectContaining({
mock: true,
zigbeeModel: ['external_converter_device'],
vendor: 'external',
await controller.start();
await flushPromises();
mocksClear.forEach((m) => m.mockClear());
expect(getZ2MDevice(devices.external_converter_device).definition).toMatchObject({
description: 'Automatically generated definition',
model: 'external_converter_device',
vendor: '',
zigbeeModel: ['external_converter_device'],
});
//-- SAVE
mockMQTTEvents.message('zigbee2mqtt/bridge/request/converter/save', stringify({name: converterName, code: converterCode}));
await flushPromises();
expect(getZ2MDevice(devices.external_converter_device).definition).toMatchObject({
description: 'external',
}),
);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/converters', stringify([{name: converterName, code: converterCode}]), {
retain: true,
qos: 0,
});
model: 'external_converter_device',
vendor: 'external',
zigbeeModel: ['external_converter_device'],
});
expect(mkdirSyncSpy).toHaveBeenCalledWith(mockBasePath, {recursive: true});
expect(writeFileSyncSpy).toHaveBeenCalledWith(converterFilePath, converterCode, 'utf8');
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenCalledTimes(1);
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenNthCalledWith(1, converterName);
expect(zhcAddDefinitionSpy).toHaveBeenCalledWith(
expect.objectContaining({
mock: true,
zigbeeModel: ['external_converter_device'],
vendor: 'external',
model: 'external_converter_device',
description: 'external',
}),
);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/converters',
stringify([{name: converterName, code: converterCode}]),
{
retain: true,
qos: 0,
},
);
//-- REMOVE
mockMQTTEvents.message('zigbee2mqtt/bridge/request/converter/remove', stringify({name: converterName}));
await flushPromises();
//-- REMOVE
mockMQTTEvents.message('zigbee2mqtt/bridge/request/converter/remove', stringify({name: converterName}));
await flushPromises();
expect(getZ2MDevice(devices.external_converter_device).definition).toMatchObject({
description: 'Automatically generated definition',
model: 'external_converter_device',
vendor: '',
zigbeeModel: ['external_converter_device'],
expect(getZ2MDevice(devices.external_converter_device).definition).toMatchObject({
description: 'Automatically generated definition',
model: 'external_converter_device',
vendor: '',
zigbeeModel: ['external_converter_device'],
});
expect(rmSyncSpy).toHaveBeenCalledWith(converterFilePath, {force: true});
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenCalledTimes(2);
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenNthCalledWith(2, converterName);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/converters', stringify([]), {retain: true, qos: 0});
});
expect(rmSyncSpy).toHaveBeenCalledWith(converterFilePath, {force: true});
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenCalledTimes(2);
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenNthCalledWith(2, converterName);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/converters', stringify([]), {retain: true, qos: 0});
});
it('returns error on invalid code', async () => {
@@ -229,14 +246,13 @@ describe('Extension: ExternalConverters', () => {
const converterCode = 'definetly not a correct javascript code';
const converterFilePath = path.join(mockBasePath, converterName);
await controller.start();
await flushPromises();
await resetExtension();
mocksClear.forEach((m) => m.mockClear());
mockMQTTEvents.message('zigbee2mqtt/bridge/request/converter/save', stringify({name: converterName, code: converterCode}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/converter/save',
expect.stringContaining(`"error":"foo.js contains invalid code`),
{retain: false, qos: 0},
@@ -248,14 +264,13 @@ describe('Extension: ExternalConverters', () => {
const converterName = 'invalid.js';
const converterFilePath = path.join(mockBasePath, converterName);
await controller.start();
await flushPromises();
await resetExtension();
mocksClear.forEach((m) => m.mockClear());
mockMQTTEvents.message('zigbee2mqtt/bridge/request/converter/remove', stringify({name: converterName}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/converter/remove',
stringify({data: {}, status: 'error', error: `${converterName} (${converterFilePath}) doesn't exists`}),
{retain: false, qos: 0},
@@ -268,8 +283,7 @@ describe('Extension: ExternalConverters', () => {
const converterCode = getFileCode('mock-external-converter.js');
const converterFilePath = path.join(mockBasePath, converterName);
await controller.start();
await flushPromises();
await resetExtension();
mocksClear.forEach((m) => m.mockClear());
const errorMsg = `Invalid definition`;
@@ -281,7 +295,7 @@ describe('Extension: ExternalConverters', () => {
mockMQTTEvents.message('zigbee2mqtt/bridge/request/converter/save', stringify({name: converterName, code: converterCode}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/response/converter/save', expect.stringContaining(errorMsg), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/response/converter/save', expect.stringContaining(errorMsg), {
retain: false,
qos: 0,
});
@@ -293,8 +307,7 @@ describe('Extension: ExternalConverters', () => {
const converterCode = getFileCode('mock-external-converter.js');
const converterFilePath = path.join(mockBasePath, converterName);
await controller.start();
await flushPromises();
await resetExtension();
mocksClear.forEach((m) => m.mockClear());
//-- SAVE
@@ -311,7 +324,7 @@ describe('Extension: ExternalConverters', () => {
mockMQTTEvents.message('zigbee2mqtt/bridge/request/converter/remove', stringify({name: converterName}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/converter/remove',
stringify({data: {}, status: 'error', error: errorMsg}),
{retain: false, qos: 0},
@@ -320,14 +333,13 @@ describe('Extension: ExternalConverters', () => {
});
it('handles invalid payloads', async () => {
await controller.start();
await flushPromises();
await resetExtension();
mocksClear.forEach((m) => m.mockClear());
mockMQTTEvents.message('zigbee2mqtt/bridge/request/converter/save', stringify({name: 'test.js', transaction: 1 /* code */}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/converter/save',
stringify({data: {}, status: 'error', error: `Invalid payload`, transaction: 1}),
{retain: false, qos: 0},
@@ -336,7 +348,7 @@ describe('Extension: ExternalConverters', () => {
mockMQTTEvents.message('zigbee2mqtt/bridge/request/converter/remove', stringify({namex: 'test.js', transaction: 2}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/converter/remove',
stringify({data: {}, status: 'error', error: `Invalid payload`, transaction: 2}),
{retain: false, qos: 0},
+91 -77
View File
@@ -1,6 +1,6 @@
import * as data from '../mocks/data';
import {mockLogger} from '../mocks/logger';
import {mockMQTT, events as mockMQTTEvents} from '../mocks/mqtt';
import {mockMQTTEndAsync, events as mockMQTTEvents, mockMQTTPublishAsync} from '../mocks/mqtt';
import {flushPromises} from '../mocks/utils';
import {devices, mockController as mockZHController, returnDevices} from '../mocks/zigbeeHerdsman';
@@ -18,15 +18,15 @@ describe('Extension: ExternalExtensions', () => {
let controller: Controller;
const mockBasePath = path.join(data.mockDir, BASE_DIR);
const existsSyncSpy = jest.spyOn(fs, 'existsSync');
const readdirSyncSpy = jest.spyOn(fs, 'readdirSync');
const mkdirSyncSpy = jest.spyOn(fs, 'mkdirSync');
const rmSyncSpy = jest.spyOn(fs, 'rmSync');
const writeFileSyncSpy = jest.spyOn(fs, 'writeFileSync');
const existsSyncSpy = vi.spyOn(fs, 'existsSync');
const readdirSyncSpy = vi.spyOn(fs, 'readdirSync');
const mkdirSyncSpy = vi.spyOn(fs, 'mkdirSync');
const rmSyncSpy = vi.spyOn(fs, 'rmSync');
const writeFileSyncSpy = vi.spyOn(fs, 'writeFileSync');
const mocksClear = [
mockMQTT.endAsync,
mockMQTT.publishAsync,
mockMQTTEndAsync,
mockMQTTPublishAsync,
mockLogger.debug,
mockLogger.error,
mockZHController.stop,
@@ -46,12 +46,21 @@ describe('Extension: ExternalExtensions', () => {
return fs.readFileSync(path.join(__dirname, '..', 'assets', BASE_DIR, fileName), 'utf8');
};
const resetExtension = async (): Promise<void> => {
await controller.enableDisableExtension(false, 'ExternalExtensions');
await controller.enableDisableExtension(true, 'ExternalExtensions');
};
beforeAll(async () => {
jest.useFakeTimers();
vi.useFakeTimers();
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
await flushPromises();
});
afterAll(async () => {
jest.useRealTimers();
vi.useRealTimers();
});
beforeEach(async () => {
@@ -60,72 +69,80 @@ describe('Extension: ExternalExtensions', () => {
data.writeDefaultState();
settings.reRead();
returnDevices.splice(0);
controller = new Controller(jest.fn(), jest.fn());
});
afterEach(() => {
fs.rmSync(mockBasePath, {recursive: true, force: true});
});
it('loads nothing from folder', async () => {
await controller.start();
await flushPromises();
expect(existsSyncSpy).toHaveBeenCalledWith(mockBasePath);
expect(readdirSyncSpy).not.toHaveBeenCalledWith(mockBasePath);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/extensions', stringify([]), {retain: true, qos: 0});
});
it('loads from folder', async () => {
useAssets();
await controller.start();
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/example2/extension', 'call2 from constructor', {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/example2/extension', 'call2 from start', {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'call from constructor', {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'call from start', {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/extensions',
stringify([
{name: 'example2Extension.js', code: getFileCode('example2Extension.js')},
{name: 'exampleExtension.js', code: getFileCode('exampleExtension.js')},
]),
{retain: true, qos: 0},
);
});
it('saves and removes from MQTT', async () => {
const extensionName = 'foo.js';
const extensionCode = getFileCode('exampleExtension.js');
const extensionFilePath = path.join(mockBasePath, extensionName);
await controller.start();
await flushPromises();
mocksClear.forEach((m) => m.mockClear());
//-- SAVE
mockMQTTEvents.message('zigbee2mqtt/bridge/request/extension/save', stringify({name: extensionName, code: extensionCode}));
await flushPromises();
expect(mkdirSyncSpy).toHaveBeenCalledWith(mockBasePath, {recursive: true});
expect(writeFileSyncSpy).toHaveBeenCalledWith(extensionFilePath, extensionCode, 'utf8');
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'call from constructor', {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'call from start', {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/extensions', stringify([{name: extensionName, code: extensionCode}]), {
retain: true,
qos: 0,
describe('from folder', () => {
beforeEach(async () => {
controller = new Controller(vi.fn(), vi.fn());
});
//-- REMOVE
mockMQTTEvents.message('zigbee2mqtt/bridge/request/extension/remove', stringify({name: extensionName}));
await flushPromises();
it('loads nothing', async () => {
await controller.start();
await flushPromises();
expect(rmSyncSpy).toHaveBeenCalledWith(extensionFilePath, {force: true});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'call from stop', {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/extensions', stringify([]), {retain: true, qos: 0});
expect(existsSyncSpy).toHaveBeenCalledWith(mockBasePath);
expect(readdirSyncSpy).not.toHaveBeenCalledWith(mockBasePath);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/extensions', stringify([]), {retain: true, qos: 0});
});
it('loads extensions', async () => {
useAssets();
await controller.start();
await flushPromises();
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/example2/extension', 'call2 from constructor', {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/example2/extension', 'call2 from start', {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'call from constructor', {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'call from start', {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/extensions',
stringify([
{name: 'example2Extension.js', code: getFileCode('example2Extension.js')},
{name: 'exampleExtension.js', code: getFileCode('exampleExtension.js')},
]),
{retain: true, qos: 0},
);
});
it('saves and removes from MQTT', async () => {
const extensionName = 'foo.js';
const extensionCode = getFileCode('exampleExtension.js');
const extensionFilePath = path.join(mockBasePath, extensionName);
await controller.start();
await flushPromises();
mocksClear.forEach((m) => m.mockClear());
//-- SAVE
mockMQTTEvents.message('zigbee2mqtt/bridge/request/extension/save', stringify({name: extensionName, code: extensionCode}));
await flushPromises();
expect(mkdirSyncSpy).toHaveBeenCalledWith(mockBasePath, {recursive: true});
expect(writeFileSyncSpy).toHaveBeenCalledWith(extensionFilePath, extensionCode, 'utf8');
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'call from constructor', {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'call from start', {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/extensions',
stringify([{name: extensionName, code: extensionCode}]),
{
retain: true,
qos: 0,
},
);
//-- REMOVE
mockMQTTEvents.message('zigbee2mqtt/bridge/request/extension/remove', stringify({name: extensionName}));
await flushPromises();
expect(rmSyncSpy).toHaveBeenCalledWith(extensionFilePath, {force: true});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/example/extension', 'call from stop', {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/extensions', stringify([]), {retain: true, qos: 0});
});
});
it('returns error on invalid code', async () => {
@@ -133,14 +150,13 @@ describe('Extension: ExternalExtensions', () => {
const extensionCode = 'definetly not a correct javascript code';
const extensionFilePath = path.join(mockBasePath, extensionName);
await controller.start();
await flushPromises();
await resetExtension();
mocksClear.forEach((m) => m.mockClear());
mockMQTTEvents.message('zigbee2mqtt/bridge/request/extension/save', stringify({name: extensionName, code: extensionCode}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/extension/save',
expect.stringContaining(`"error":"${extensionName} contains invalid code`),
{retain: false, qos: 0},
@@ -152,14 +168,13 @@ describe('Extension: ExternalExtensions', () => {
const converterName = 'invalid.js';
const converterFilePath = path.join(mockBasePath, converterName);
await controller.start();
await flushPromises();
await resetExtension();
mocksClear.forEach((m) => m.mockClear());
mockMQTTEvents.message('zigbee2mqtt/bridge/request/extension/remove', stringify({name: converterName}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/extension/remove',
stringify({data: {}, status: 'error', error: `${converterName} (${converterFilePath}) doesn't exists`}),
{retain: false, qos: 0},
@@ -168,14 +183,13 @@ describe('Extension: ExternalExtensions', () => {
});
it('handles invalid payloads', async () => {
await controller.start();
await flushPromises();
await resetExtension();
mocksClear.forEach((m) => m.mockClear());
mockMQTTEvents.message('zigbee2mqtt/bridge/request/extension/save', stringify({name: 'test.js', transaction: 1 /* code */}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/extension/save',
stringify({data: {}, status: 'error', error: `Invalid payload`, transaction: 1}),
{retain: false, qos: 0},
@@ -184,7 +198,7 @@ describe('Extension: ExternalExtensions', () => {
mockMQTTEvents.message('zigbee2mqtt/bridge/request/extension/remove', stringify({namex: 'test.js', transaction: 2}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/extension/remove',
stringify({data: {}, status: 'error', error: `Invalid payload`, transaction: 2}),
{retain: false, qos: 0},
+59 -54
View File
@@ -1,6 +1,6 @@
import * as data from '../mocks/data';
import {mockLogger} from '../mocks/logger';
import {mockMQTT} from '../mocks/mqtt';
import {mockMQTTPublishAsync} from '../mocks/mqtt';
import {EventHandler, flushPromises} from '../mocks/utils';
import {devices} from '../mocks/zigbeeHerdsman';
@@ -15,26 +15,26 @@ import * as settings from '../../lib/util/settings';
let mockHTTPOnRequest: (request: {url: string}, response: number) => void;
const mockHTTPEvents: Record<string, EventHandler> = {};
const mockHTTP = {
listen: jest.fn(),
listen: vi.fn(),
on: (event: string, handler: EventHandler): void => {
mockHTTPEvents[event] = handler;
},
close: jest.fn().mockImplementation((cb) => cb()),
close: vi.fn<(cb: (err?: Error) => void) => void>((cb) => cb()),
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let mockHTTPSOnRequest: (request: {url: string}, response: number) => void;
const mockHTTPSEvents: Record<string, EventHandler> = {};
const mockHTTPS = {
listen: jest.fn(),
listen: vi.fn(),
on: (event: string, handler: EventHandler): void => {
mockHTTPSEvents[event] = handler;
},
close: jest.fn().mockImplementation((cb) => cb()),
close: vi.fn<(cb: (err?: Error) => void) => void>((cb) => cb()),
};
const mockWSocket = {
close: jest.fn(),
close: vi.fn<(cb: (err?: Error) => void) => void>(),
};
const mockWSClientEvents: Record<string, EventHandler> = {};
@@ -42,8 +42,8 @@ const mockWSClient = {
on: (event: string, handler: EventHandler): void => {
mockWSClientEvents[event] = handler;
},
send: jest.fn(),
terminate: jest.fn(),
send: vi.fn<(data: string) => void>(),
terminate: vi.fn<() => void>(),
readyState: 'close',
};
const mockWSEvents: Record<string, EventHandler> = {};
@@ -53,57 +53,62 @@ const mockWS = {
on: (event: string, handler: EventHandler): void => {
mockWSEvents[event] = handler;
},
handleUpgrade: jest.fn().mockImplementation((request, socket, head, cb) => {
handleUpgrade: vi.fn().mockImplementation((request, socket, head, cb) => {
cb(mockWSocket);
}),
emit: jest.fn(),
close: jest.fn(),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
emit: vi.fn<(eventName: string, ...args: any[]) => void>(),
close: vi.fn<(code?: number, data?: string | Buffer) => void>(),
};
let mockNodeStaticPath: string = '';
const mockNodeStatic = jest.fn();
const mockNodeStatic = vi.fn();
const mockFinalHandler = jest.fn();
const mockFinalHandler = vi.fn();
jest.mock('node:http', () => ({
createServer: jest.fn().mockImplementation((onRequest) => {
vi.mock('node:http', () => ({
createServer: vi.fn().mockImplementation((onRequest) => {
mockHTTPOnRequest = onRequest;
return mockHTTP;
}),
Agent: jest.fn(),
Agent: vi.fn(),
}));
jest.mock('node:https', () => ({
createServer: jest.fn().mockImplementation((onRequest) => {
vi.mock('node:https', () => ({
createServer: vi.fn().mockImplementation((onRequest) => {
mockHTTPSOnRequest = onRequest;
return mockHTTPS;
}),
Agent: jest.fn(),
Agent: vi.fn(),
}));
jest.mock('express-static-gzip', () =>
jest.fn().mockImplementation((path) => {
vi.mock('express-static-gzip', () => ({
default: vi.fn().mockImplementation((path) => {
mockNodeStaticPath = path;
return mockNodeStatic;
}),
);
jest.mock('zigbee2mqtt-frontend', () => ({
getPath: (): string => 'my/dummy/path',
}));
jest.mock('ws', () => ({
OPEN: 'open',
Server: jest.fn().mockImplementation(() => {
return mockWS;
}),
vi.mock('zigbee2mqtt-frontend', () => ({
default: {
getPath: (): string => 'my/dummy/path',
},
}));
jest.mock('finalhandler', () =>
jest.fn().mockImplementation(() => {
vi.mock('ws', () => ({
default: {
OPEN: 'open',
Server: vi.fn().mockImplementation(() => {
return mockWS;
}),
},
}));
vi.mock('finalhandler', () => ({
default: vi.fn().mockImplementation(() => {
return mockFinalHandler;
}),
);
}));
const mocksClear = [
mockHTTP.close,
@@ -118,7 +123,7 @@ const mocksClear = [
mockWSClient.terminate,
mockNodeStatic,
mockFinalHandler,
mockMQTT.publishAsync,
mockMQTTPublishAsync,
mockLogger.error,
];
@@ -126,7 +131,7 @@ describe('Extension: Frontend', () => {
let controller: Controller;
beforeAll(async () => {
jest.useFakeTimers();
vi.useFakeTimers();
});
beforeEach(async () => {
@@ -142,7 +147,7 @@ describe('Extension: Frontend', () => {
});
afterAll(async () => {
jest.useRealTimers();
vi.useRealTimers();
});
afterEach(async () => {
@@ -150,7 +155,7 @@ describe('Extension: Frontend', () => {
});
it('Start/stop with defaults', async () => {
controller = new Controller(jest.fn(), jest.fn());
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
expect(mockNodeStaticPath).toBe('my/dummy/path');
expect(mockHTTP.listen).toHaveBeenCalledWith(8081, '127.0.0.1');
@@ -163,7 +168,7 @@ describe('Extension: Frontend', () => {
it('Start/stop without host', async () => {
settings.set(['frontend'], {enabled: true, port: 8081});
controller = new Controller(jest.fn(), jest.fn());
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
expect(mockNodeStaticPath).toBe('my/dummy/path');
expect(mockHTTP.listen).toHaveBeenCalledWith(8081);
@@ -176,7 +181,7 @@ describe('Extension: Frontend', () => {
it('Start/stop unix socket', async () => {
settings.set(['frontend', 'host'], '/tmp/zigbee2mqtt.sock');
controller = new Controller(jest.fn(), jest.fn());
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
expect(mockNodeStaticPath).toBe('my/dummy/path');
expect(mockHTTP.listen).toHaveBeenCalledWith('/tmp/zigbee2mqtt.sock');
@@ -190,7 +195,7 @@ describe('Extension: Frontend', () => {
it('Start/stop HTTPS valid', async () => {
settings.set(['frontend', 'ssl_cert'], path.join(__dirname, '..', 'assets', 'certs', 'dummy.crt'));
settings.set(['frontend', 'ssl_key'], path.join(__dirname, '..', 'assets', 'certs', 'dummy.key'));
controller = new Controller(jest.fn(), jest.fn());
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
expect(mockHTTP.listen).not.toHaveBeenCalledWith(8081, '127.0.0.1');
expect(mockHTTPS.listen).toHaveBeenCalledWith(8081, '127.0.0.1');
@@ -199,7 +204,7 @@ describe('Extension: Frontend', () => {
it('Start/stop HTTPS invalid : missing config', async () => {
settings.set(['frontend', 'ssl_cert'], path.join(__dirname, '..', 'assets', 'certs', 'dummy.crt'));
controller = new Controller(jest.fn(), jest.fn());
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
expect(mockHTTP.listen).toHaveBeenCalledWith(8081, '127.0.0.1');
expect(mockHTTPS.listen).not.toHaveBeenCalledWith(8081, '127.0.0.1');
@@ -209,7 +214,7 @@ describe('Extension: Frontend', () => {
it('Start/stop HTTPS invalid : missing file', async () => {
settings.set(['frontend', 'ssl_cert'], 'filesNotExists.crt');
settings.set(['frontend', 'ssl_key'], path.join(__dirname, '..', 'assets', 'certs', 'dummy.key'));
controller = new Controller(jest.fn(), jest.fn());
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
expect(mockHTTP.listen).toHaveBeenCalledWith(8081, '127.0.0.1');
expect(mockHTTPS.listen).not.toHaveBeenCalledWith(8081, '127.0.0.1');
@@ -217,25 +222,25 @@ describe('Extension: Frontend', () => {
});
it('Websocket interaction', async () => {
controller = new Controller(jest.fn(), jest.fn());
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
mockWSClient.readyState = 'open';
mockWS.clients.push(mockWSClient);
await mockWSEvents.connection(mockWSClient);
const allTopics = mockWSClient.send.mock.calls.map((m) => JSON.parse(m).topic);
const allTopics = mockWSClient.send.mock.calls.map(([m]) => JSON.parse(m).topic);
expect(allTopics).toContain('bridge/devices');
expect(allTopics).toContain('bridge/info');
expect(mockWSClient.send).toHaveBeenCalledWith(stringify({topic: 'bridge/state', payload: {state: 'online'}}));
expect(mockWSClient.send).toHaveBeenCalledWith(stringify({topic: 'remote', payload: {brightness: 255}}));
// Message
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockWSClient.send.mockClear();
mockWSClientEvents.message(stringify({topic: 'bulb_color/set', payload: {state: 'ON'}}), false);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bulb_color',
stringify({
state: 'ON',
@@ -288,10 +293,10 @@ describe('Extension: Frontend', () => {
});
it('onRequest/onUpgrade', async () => {
controller = new Controller(jest.fn(), jest.fn());
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
const mockSocket = {destroy: jest.fn()};
const mockSocket = {destroy: vi.fn()};
mockHTTPEvents.upgrade({url: 'http://localhost:8080/api'}, mockSocket, 3);
expect(mockWS.handleUpgrade).toHaveBeenCalledTimes(1);
expect(mockSocket.destroy).toHaveBeenCalledTimes(0);
@@ -305,7 +310,7 @@ describe('Extension: Frontend', () => {
});
it('Static server', async () => {
controller = new Controller(jest.fn(), jest.fn());
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
expect(mockHTTP.listen).toHaveBeenCalledWith(8081, '127.0.0.1');
@@ -314,10 +319,10 @@ describe('Extension: Frontend', () => {
it('Authentification', async () => {
const authToken = 'sample-secure-token';
settings.set(['frontend', 'auth_token'], authToken);
controller = new Controller(jest.fn(), jest.fn());
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
const mockSocket = {destroy: jest.fn()};
const mockSocket = {destroy: vi.fn()};
mockHTTPEvents.upgrade({url: '/api'}, mockSocket, mockWSocket);
expect(mockWS.handleUpgrade).toHaveBeenCalledTimes(1);
expect(mockSocket.destroy).toHaveBeenCalledTimes(0);
@@ -340,7 +345,7 @@ describe('Extension: Frontend', () => {
it.each(['/z2m/', '/z2m'])('Works with non-default base url %s', async (baseUrl) => {
settings.set(['frontend', 'base_url'], baseUrl);
controller = new Controller(jest.fn(), jest.fn());
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
expect(ws.Server).toHaveBeenCalledWith({noServer: true, path: '/z2m/api'});
@@ -366,7 +371,7 @@ describe('Extension: Frontend', () => {
it('Works with non-default complex base url', async () => {
const baseUrl = '/z2m-more++/c0mplex.url/';
settings.set(['frontend', 'base_url'], baseUrl);
controller = new Controller(jest.fn(), jest.fn());
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
expect(ws.Server).toHaveBeenCalledWith({noServer: true, path: '/z2m-more++/c0mplex.url/api'});
+151 -151
View File
@@ -1,6 +1,6 @@
import * as data from '../mocks/data';
import {mockLogger} from '../mocks/logger';
import {mockMQTT, events as mockMQTTEvents} from '../mocks/mqtt';
import {events as mockMQTTEvents, mockMQTTPublishAsync} from '../mocks/mqtt';
import {flushPromises} from '../mocks/utils';
import {devices, groups, events as mockZHEvents, resetGroupMembers, returnDevices} from '../mocks/zigbeeHerdsman';
@@ -25,21 +25,21 @@ describe('Extension: Groups', () => {
let controller: Controller;
beforeAll(async () => {
jest.useFakeTimers();
controller = new Controller(jest.fn(), jest.fn());
vi.useFakeTimers();
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
await flushPromises();
});
afterAll(async () => {
jest.useRealTimers();
vi.useRealTimers();
});
beforeEach(() => {
resetGroupMembers();
data.writeDefaultConfiguration();
settings.reRead();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
groups.gledopto_group.command.mockClear();
zhcToZigbee.__clearStore__();
// @ts-expect-error private
@@ -52,21 +52,21 @@ describe('Extension: Groups', () => {
const group = groups.group_1;
group.members.push(endpoint);
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
const payload = {data: {onOff: 1}, cluster: 'genOnOff', device, endpoint, type: 'attributeReport', linkquality: 10};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0});
});
it('Should not republish identical optimistic group states', async () => {
const device1 = devices.bulb_2;
const device2 = devices.bulb_color_2;
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockZHEvents.message({
data: {onOff: 1},
cluster: 'genOnOff',
@@ -84,13 +84,13 @@ describe('Extension: Groups', () => {
linkquality: 10,
});
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(6);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_tradfri_remote', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_2', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color_2', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_with_tradfri', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/ha_discovery_group', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/switch_group', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(6);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_tradfri_remote', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_2', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color_2', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_with_tradfri', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/ha_discovery_group', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/switch_group', stringify({state: 'ON'}), {retain: false, qos: 0});
});
it('Should publish state change of all members when a group changes its state', async () => {
@@ -99,12 +99,12 @@ describe('Extension: Groups', () => {
const group = groups.group_1;
group.members.push(endpoint);
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0});
});
it('Should not publish state change when group changes state and device is disabled', async () => {
@@ -114,11 +114,11 @@ describe('Extension: Groups', () => {
group.members.push(endpoint);
settings.set(['devices', device.ieeeAddr, 'disabled'], true);
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0});
});
it('Should publish state change for group when members state change', async () => {
@@ -128,37 +128,37 @@ describe('Extension: Groups', () => {
const group = groups.group_1;
group.members.push(endpoint);
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0});
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'OFF'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'OFF'}), {retain: false, qos: 0});
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0});
});
it('Should publish state of device with endpoint name', async () => {
const group = groups.gledopto_group;
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/gledopto_group/set', stringify({state: 'ON'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/GLEDOPTO_2ID', stringify({state_cct: 'ON'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/gledopto_group', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/GLEDOPTO_2ID', stringify({state_cct: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/gledopto_group', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(group.command).toHaveBeenCalledTimes(1);
expect(group.command).toHaveBeenCalledWith('genOnOff', 'on', {}, {});
});
@@ -166,12 +166,12 @@ describe('Extension: Groups', () => {
it('Should publish state of group when specific state of specific endpoint is changed', async () => {
const group = groups.gledopto_group;
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/GLEDOPTO_2ID/set', stringify({state_cct: 'ON'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/GLEDOPTO_2ID', stringify({state_cct: 'ON'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/gledopto_group', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/GLEDOPTO_2ID', stringify({state_cct: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/gledopto_group', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(group.command).toHaveBeenCalledTimes(0);
});
@@ -182,15 +182,15 @@ describe('Extension: Groups', () => {
group.members.push(endpoint);
settings.set(['groups'], {1: {friendly_name: 'group_1', retain: false, filtered_attributes: ['brightness']}});
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON', brightness: 100}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'ON', brightness: 100}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'ON', brightness: 100}), {
retain: false,
qos: 0,
});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0});
});
it('Shouldnt publish group state change when a group is not optimistic', async () => {
@@ -200,13 +200,13 @@ describe('Extension: Groups', () => {
group.members.push(endpoint);
settings.set(['groups'], {1: {friendly_name: 'group_1', optimistic: false, retain: false}});
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
const payload = {data: {onOff: 1}, cluster: 'genOnOff', device, endpoint, type: 'attributeReport', linkquality: 10};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0});
});
it('Should publish state change of another group with shared device when a group changes its state', async () => {
@@ -215,13 +215,13 @@ describe('Extension: Groups', () => {
groups.group_1.members.push(endpoint);
groups.group_2.members.push(endpoint);
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_2', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_2', stringify({state: 'ON'}), {retain: false, qos: 0});
});
it('Should not publish state change off if any lights within are still on when changed via device', async () => {
@@ -235,12 +235,12 @@ describe('Extension: Groups', () => {
await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
});
it('Should publish state change off if any lights within are still on when changed via device when off_state: last_member_state is used', async () => {
@@ -257,13 +257,13 @@ describe('Extension: Groups', () => {
await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(1, 'zigbee2mqtt/group_1', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(2, 'zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(1, 'zigbee2mqtt/group_1', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(2, 'zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
});
it('Should not publish state change off if any lights within with non default-ep are still on when changed via device', async () => {
@@ -277,12 +277,12 @@ describe('Extension: Groups', () => {
await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
});
it('Should not publish state change off if any lights within are still on when changed via device with non default-ep', async () => {
@@ -299,12 +299,12 @@ describe('Extension: Groups', () => {
await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/wall_switch_double/set', stringify({state_left: 'OFF'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/wall_switch_double', stringify({state_left: 'OFF', state_right: 'ON'}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/wall_switch_double', stringify({state_left: 'OFF', state_right: 'ON'}), {
retain: false,
qos: 0,
});
@@ -324,15 +324,15 @@ describe('Extension: Groups', () => {
await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'}));
await mockMQTTEvents.message('zigbee2mqtt/wall_switch_double/set', stringify({state_left: 'OFF'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/wall_switch_double', stringify({state_left: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/wall_switch_double', stringify({state_left: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'OFF'}), {retain: false, qos: 0});
});
it('Should not publish state change off if any lights within are still on when changed via shared group', async () => {
@@ -346,13 +346,13 @@ describe('Extension: Groups', () => {
await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/group_2/set', stringify({state: 'OFF'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_2', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_2', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
});
it('Should publish state change off if all lights within turn off', async () => {
@@ -369,15 +369,15 @@ describe('Extension: Groups', () => {
await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'}));
await mockMQTTEvents.message('zigbee2mqtt/bulb/set', stringify({state: 'OFF'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({state: 'OFF'}), {retain: true, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({state: 'OFF'}), {retain: true, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'OFF'}), {retain: false, qos: 0});
});
it('Should only update group state with changed properties', async () => {
@@ -391,26 +391,26 @@ describe('Extension: Groups', () => {
settings.set(['groups'], {
1: {friendly_name: 'group_1', retain: false},
});
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF', color_temp: 200}));
await mockMQTTEvents.message('zigbee2mqtt/bulb/set', stringify({state: 'ON', color_temp: 250}));
await flushPromises();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({color_temp: 300}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bulb_color',
stringify({color_mode: 'color_temp', color_temp: 300, state: 'OFF'}),
{retain: false, qos: 0},
);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({color_mode: 'color_temp', color_temp: 300, state: 'ON'}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({color_mode: 'color_temp', color_temp: 300, state: 'ON'}), {
retain: true,
qos: 0,
});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/group_1',
stringify({color_mode: 'color_temp', color_temp: 300, state: 'ON'}),
{retain: false, qos: 0},
@@ -431,16 +431,16 @@ describe('Extension: Groups', () => {
await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'}));
await flushPromises();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
// @ts-expect-error private
controller.state.state = {};
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({state: 'OFF'}), {retain: false, qos: 0});
});
it('Add to group via MQTT', async () => {
@@ -448,15 +448,15 @@ describe('Extension: Groups', () => {
const endpoint = device.getEndpoint(1);
const group = groups.group_1;
expect(group.members.length).toBe(0);
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message(
'zigbee2mqtt/bridge/request/group/members/add',
stringify({transaction: '123', group: 'group_1', device: 'bulb_color'}),
);
await flushPromises();
expect(group.members).toStrictEqual([endpoint]);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
stringify({data: {device: 'bulb_color', endpoint: 'default', group: 'group_1'}, transaction: '123', status: 'ok'}),
{retain: false, qos: 0},
@@ -472,12 +472,12 @@ describe('Extension: Groups', () => {
throw new Error('timeout');
});
await flushPromises();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: 'bulb_color'}));
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(mockMQTT.publishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
stringify({data: {}, status: 'error', error: 'Failed to add from group (timeout)'}),
{retain: false, qos: 0},
@@ -490,12 +490,12 @@ describe('Extension: Groups', () => {
const group = groups['group/with/slashes'];
settings.set(['groups'], {99: {friendly_name: 'group/with/slashes', retain: false}});
expect(group.members.length).toBe(0);
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group/with/slashes', device: 'bulb_color'}));
await flushPromises();
expect(group.members).toStrictEqual([endpoint]);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
stringify({data: {device: 'bulb_color', endpoint: 'default', group: 'group/with/slashes'}, status: 'ok'}),
{retain: false, qos: 0},
@@ -507,15 +507,15 @@ describe('Extension: Groups', () => {
const endpoint = device.getEndpoint(3)!;
const group = groups.group_1;
expect(group.members.length).toBe(0);
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message(
'zigbee2mqtt/bridge/request/group/members/add',
stringify({group: 'group_1', device: 'wall_switch_double', endpoint: 'right'}),
);
await flushPromises();
expect(group.members).toStrictEqual([endpoint]);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
stringify({data: {device: 'wall_switch_double', endpoint: 'right', group: 'group_1'}, status: 'ok'}),
{retain: false, qos: 0},
@@ -527,7 +527,7 @@ describe('Extension: Groups', () => {
const endpoint = device.getEndpoint(3)!;
const group = groups.group_1;
expect(group.members.length).toBe(0);
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message(
'zigbee2mqtt/bridge/request/group/members/add',
stringify({group: 'group_1', device: 'wall_switch_double', endpoint: 'right'}),
@@ -539,8 +539,8 @@ describe('Extension: Groups', () => {
);
await flushPromises();
expect(group.members).toStrictEqual([endpoint]);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
stringify({data: {device: 'wall_switch_double', endpoint: 'right', group: 'group_1'}, status: 'ok'}),
{retain: false, qos: 0},
@@ -552,12 +552,12 @@ describe('Extension: Groups', () => {
const endpoint = device.getEndpoint(1)!;
const group = groups.group_1;
group.members.push(endpoint);
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1', device: 'bulb_color'}));
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/remove',
stringify({data: {device: 'bulb_color', endpoint: 'default', group: 'group_1'}, status: 'ok'}),
{retain: false, qos: 0},
@@ -569,15 +569,15 @@ describe('Extension: Groups', () => {
const endpoint = device.getEndpoint(1)!;
const group = groups.group_1;
group.members.push(endpoint);
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message(
'zigbee2mqtt/bridge/request/group/members/remove',
stringify({group: 'group_1', device: 'bulb_color', skip_disable_reporting: true}),
);
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/remove',
stringify({data: {device: 'bulb_color', endpoint: 'default', group: 'group_1'}, status: 'ok'}),
{retain: false, qos: 0},
@@ -589,15 +589,15 @@ describe('Extension: Groups', () => {
const endpoint = device.getEndpoint(3)!;
const group = groups.group_1;
group.members.push(endpoint);
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message(
'zigbee2mqtt/bridge/request/group/members/remove',
stringify({group: 'group_1', device: '0x0017880104e45542', endpoint: '3'}),
);
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/remove',
stringify({data: {device: '0x0017880104e45542', endpoint: '3', group: 'group_1'}, status: 'ok'}),
{retain: false, qos: 0},
@@ -609,15 +609,15 @@ describe('Extension: Groups', () => {
const endpoint = device.getEndpoint(3)!;
const group = groups.group_1;
group.members.push(endpoint);
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message(
'zigbee2mqtt/bridge/request/group/members/remove',
stringify({group: 'group_1', device: 'wall_switch_double', endpoint: '3'}),
);
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/remove',
stringify({data: {device: 'wall_switch_double', endpoint: '3', group: 'group_1'}, status: 'ok'}),
{retain: false, qos: 0},
@@ -629,15 +629,15 @@ describe('Extension: Groups', () => {
const endpoint = device.getEndpoint(3)!;
const group = groups.group_1;
group.members.push(endpoint);
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message(
'zigbee2mqtt/bridge/request/group/members/remove',
stringify({group: 'group_1', device: '0x0017880104e45542', endpoint: 'right'}),
);
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/remove',
stringify({data: {device: '0x0017880104e45542', endpoint: 'right', group: 'group_1'}, status: 'ok'}),
{retain: false, qos: 0},
@@ -647,12 +647,12 @@ describe('Extension: Groups', () => {
it('Remove from group all', async () => {
const group = groups.group_1;
groups.group_1.members.push(devices.QBKG03LM.endpoints[2]);
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/members/remove_all', stringify({device: '0x0017880104e45542', endpoint: 'right'}));
await flushPromises();
expect(group.members).toStrictEqual([]);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/remove_all',
stringify({data: {device: '0x0017880104e45542', endpoint: 'right'}, status: 'ok'}),
{retain: false, qos: 0},
@@ -661,11 +661,11 @@ describe('Extension: Groups', () => {
it('Error when adding to non-existing group', async () => {
mockLogger.error.mockClear();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/members/remove', stringify({group: 'group_1_not_existing', device: 'bulb_color'}));
await flushPromises();
expect(mockMQTT.publishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/remove',
stringify({data: {}, status: 'error', error: "Group 'group_1_not_existing' does not exist"}),
{retain: false, qos: 0},
@@ -674,11 +674,11 @@ describe('Extension: Groups', () => {
it('Error when adding a non-existing device', async () => {
mockLogger.error.mockClear();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', device: 'bulb_color_not_existing'}));
await flushPromises();
expect(mockMQTT.publishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
stringify({data: {}, status: 'error', error: "Device 'bulb_color_not_existing' does not exist"}),
{retain: false, qos: 0},
@@ -687,14 +687,14 @@ describe('Extension: Groups', () => {
it('Error when adding a non-existing endpoint', async () => {
mockLogger.error.mockClear();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message(
'zigbee2mqtt/bridge/request/group/members/add',
stringify({group: 'group_1', device: 'bulb_color', endpoint: 'not_existing_endpoint'}),
);
await flushPromises();
expect(mockMQTT.publishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
stringify({data: {}, status: 'error', error: "Device 'bulb_color' does not have endpoint 'not_existing_endpoint'"}),
{retain: false, qos: 0},
@@ -703,11 +703,11 @@ describe('Extension: Groups', () => {
it('Error when invalid payload', async () => {
mockLogger.error.mockClear();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'group_1', devicez: 'bulb_color'}));
await flushPromises();
expect(mockMQTT.publishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
stringify({data: {}, status: 'error', error: 'Invalid payload'}),
{retain: false, qos: 0},
@@ -716,11 +716,11 @@ describe('Extension: Groups', () => {
it('Error when add/remove with invalid payload', async () => {
mockLogger.error.mockClear();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
mockMQTTEvents.message('zigbee2mqtt/bridge/request/group/members/add', stringify({groupz: 'group_1', device: 'bulb_color'}));
await flushPromises();
expect(mockMQTT.publishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).not.toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), expect.any(Object));
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/group/members/add',
stringify({data: {}, status: 'error', error: 'Invalid payload'}),
{retain: false, qos: 0},
@@ -734,38 +734,38 @@ describe('Extension: Groups', () => {
group.members.push(bulbColor.getEndpoint(1)!);
group.members.push(bulbColorTemp.getEndpoint(1)!);
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({color_temp: 50}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({color_mode: 'color_temp', color_temp: 50}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb_color', stringify({color_mode: 'color_temp', color_temp: 50}), {
retain: false,
qos: 0,
});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({color_mode: 'color_temp', color_temp: 50}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/group_1', stringify({color_mode: 'color_temp', color_temp: 50}), {
retain: false,
qos: 0,
});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({color_mode: 'color_temp', color_temp: 50}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({color_mode: 'color_temp', color_temp: 50}), {
retain: true,
qos: 0,
});
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/group_1/set', stringify({color: {x: 0.5, y: 0.3}}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bulb_color',
stringify({color: {x: 0.5, y: 0.3}, color_mode: 'xy', color_temp: 548}),
{retain: false, qos: 0},
);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/group_1',
stringify({color: {x: 0.5, y: 0.3}, color_mode: 'xy', color_temp: 548}),
{retain: false, qos: 0},
);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({color_mode: 'color_temp', color_temp: 548}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({color_mode: 'color_temp', color_temp: 548}), {
retain: true,
qos: 0,
});
File diff suppressed because it is too large Load Diff
+23 -26
View File
@@ -1,6 +1,6 @@
import * as data from '../mocks/data';
import {mockLogger} from '../mocks/logger';
import {mockMQTT, events as mockMQTTEvents} from '../mocks/mqtt';
import {events as mockMQTTEvents, mockMQTTPublishAsync} from '../mocks/mqtt';
import * as mockSleep from '../mocks/sleep';
import {flushPromises} from '../mocks/utils';
import {devices, events as mockZHEvents, returnDevices} from '../mocks/zigbeeHerdsman';
@@ -23,7 +23,7 @@ returnDevices.push(
devices.external_converter_device.ieeeAddr,
);
const mocksClear = [mockMQTT.publishAsync, mockLogger.warning, mockLogger.debug];
const mocksClear = [mockMQTTPublishAsync, mockLogger.warning, mockLogger.debug];
describe('Extension: NetworkMap', () => {
let controller: Controller;
@@ -96,9 +96,9 @@ describe('Extension: NetworkMap', () => {
};
beforeAll(async () => {
jest.useFakeTimers();
vi.useFakeTimers();
mockSleep.mock();
jest.spyOn(Date, 'now').mockReturnValue(10000);
vi.spyOn(Date, 'now').mockReturnValue(10000);
data.writeDefaultConfiguration();
settings.reRead();
data.writeEmptyState();
@@ -107,7 +107,7 @@ describe('Extension: NetworkMap', () => {
path.join(__dirname, '..', 'assets', 'external_converters', 'mock-external-converter.js'),
path.join(data.mockDir, 'external_converters', 'mock-external-converter.js'),
);
controller = new Controller(jest.fn(), jest.fn());
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
});
@@ -124,15 +124,15 @@ describe('Extension: NetworkMap', () => {
afterAll(async () => {
mockSleep.restore();
fs.rmSync(path.join(data.mockDir, 'external_converters'), {recursive: true});
jest.useRealTimers();
vi.useRealTimers();
});
it('Output raw networkmap', async () => {
mock();
mockMQTTEvents.message('zigbee2mqtt/bridge/request/networkmap', stringify({type: 'raw', routes: true}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/response/networkmap');
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/response/networkmap');
const expected = {
data: {
@@ -315,7 +315,7 @@ describe('Extension: NetworkMap', () => {
},
status: 'ok',
};
const actual = JSON.parse(mockMQTT.publishAsync.mock.calls[0][1]);
const actual = JSON.parse(mockMQTTPublishAsync.mock.calls[0][1]);
expect(actual).toStrictEqual(expected);
});
@@ -329,8 +329,8 @@ describe('Extension: NetworkMap', () => {
await mockZHEvents.message(payload);
mockMQTTEvents.message('zigbee2mqtt/bridge/request/networkmap', stringify({type: 'graphviz', routes: true}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/response/networkmap');
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/response/networkmap');
const expected = `digraph G {
node[shape=record];
@@ -350,7 +350,7 @@ describe('Extension: NetworkMap', () => {
}`;
const expectedLines = expected.split('\n');
const actualLines = JSON.parse(mockMQTT.publishAsync.mock.calls[0][1]).data.value.split('\n');
const actualLines = JSON.parse(mockMQTTPublishAsync.mock.calls[0][1]).data.value.split('\n');
for (let i = 0; i < expectedLines.length; i++) {
expect(actualLines[i].trim()).toStrictEqual(expectedLines[i].trim());
@@ -367,8 +367,8 @@ describe('Extension: NetworkMap', () => {
await mockZHEvents.message(payload);
mockMQTTEvents.message('zigbee2mqtt/bridge/request/networkmap', stringify({type: 'plantuml', routes: true}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/response/networkmap');
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/response/networkmap');
const expected = `' paste into: https://www.planttext.com/
@@ -451,7 +451,7 @@ describe('Extension: NetworkMap', () => {
@enduml`;
const expectedLines = expected.split('\n');
const actualLines = JSON.parse(mockMQTT.publishAsync.mock.calls[0][1]).data.value.split('\n');
const actualLines = JSON.parse(mockMQTTPublishAsync.mock.calls[0][1]).data.value.split('\n');
for (let i = 0; i < expectedLines.length; i++) {
expect(actualLines[i].trim()).toStrictEqual(expectedLines[i].trim());
@@ -460,11 +460,10 @@ describe('Extension: NetworkMap', () => {
it('Should throw error when requesting invalid type', async () => {
mock();
mockMQTT.publishAsync.mockClear();
mockMQTTEvents.message('zigbee2mqtt/bridge/request/networkmap', 'not_existing');
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/networkmap',
stringify({data: {}, status: 'error', error: "Type 'not_existing' not supported, allowed are: raw,graphviz,plantuml"}),
{retain: false, qos: 0},
@@ -474,11 +473,10 @@ describe('Extension: NetworkMap', () => {
it('Should exclude disabled devices from networkmap', async () => {
settings.set(['devices', '0x000b57fffec6a5b2', 'disabled'], true);
mock();
mockMQTT.publishAsync.mockClear();
mockMQTTEvents.message('zigbee2mqtt/bridge/request/networkmap', stringify({type: 'raw', routes: true}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/response/networkmap');
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/response/networkmap');
const expected = {
data: {
@@ -619,7 +617,7 @@ describe('Extension: NetworkMap', () => {
},
status: 'ok',
};
const actual = JSON.parse(mockMQTT.publishAsync.mock.calls[0][1]);
const actual = JSON.parse(mockMQTTPublishAsync.mock.calls[0][1]);
expect(actual).toStrictEqual(expected);
});
@@ -627,11 +625,10 @@ describe('Extension: NetworkMap', () => {
settings.set(['devices', '0x000b57fffec6a5b2', 'disabled'], true);
mock();
devices.bulb.lqi.mockRejectedValueOnce('failed');
mockMQTT.publishAsync.mockClear();
mockMQTTEvents.message('zigbee2mqtt/bridge/request/networkmap', stringify({type: 'raw', routes: true}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/response/networkmap');
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/response/networkmap');
const expected = {
data: {
@@ -772,7 +769,7 @@ describe('Extension: NetworkMap', () => {
},
status: 'ok',
};
const actual = JSON.parse(mockMQTT.publishAsync.mock.calls[0][1]);
const actual = JSON.parse(mockMQTTPublishAsync.mock.calls[0][1]);
expect(actual).toStrictEqual(expected);
});
});
+9 -9
View File
@@ -1,6 +1,6 @@
import * as data from '../mocks/data';
import {mockLogger} from '../mocks/logger';
import {mockMQTT} from '../mocks/mqtt';
import {mockMQTTPublishAsync} from '../mocks/mqtt';
import {flushPromises} from '../mocks/utils';
import {devices, events as mockZHEvents} from '../mocks/zigbeeHerdsman';
@@ -9,23 +9,23 @@ import * as zhc from 'zigbee-herdsman-converters';
import {Controller} from '../../lib/controller';
import * as settings from '../../lib/util/settings';
const mockOnEvent = jest.fn();
const mockLivoloOnEvent = jest.fn();
const mockOnEvent = vi.fn();
const mockLivoloOnEvent = vi.fn();
const mappedLivolo = zhc.findByModel('TI0001')!;
mappedLivolo.onEvent = mockLivoloOnEvent;
// @ts-expect-error mock
zhc.onEvent = mockOnEvent;
const mocksClear = [mockMQTT.publishAsync, mockLogger.warning, mockLogger.debug];
const mocksClear = [mockMQTTPublishAsync, mockLogger.warning, mockLogger.debug];
describe('Extension: OnEvent', () => {
let controller: Controller;
beforeEach(async () => {
jest.useFakeTimers();
vi.useFakeTimers();
data.writeDefaultConfiguration();
settings.reRead();
controller = new Controller(jest.fn(), jest.fn());
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
await flushPromises();
});
@@ -40,7 +40,7 @@ describe('Extension: OnEvent', () => {
});
afterAll(async () => {
jest.useRealTimers();
vi.useRealTimers();
});
it('Should call with start event', async () => {
@@ -81,9 +81,9 @@ describe('Extension: OnEvent', () => {
);
// Test deviceExposesChanged
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
console.log(mockLivoloOnEvent.mock.calls[0][5].deviceExposesChanged());
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/devices');
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/devices');
});
it('Should call index onEvent with zigbee event', async () => {
+31 -31
View File
@@ -1,6 +1,6 @@
import * as data from '../mocks/data';
import {mockLogger} from '../mocks/logger';
import {mockMQTT, events as mockMQTTEvents} from '../mocks/mqtt';
import {events as mockMQTTEvents, mockMQTTPublishAsync} from '../mocks/mqtt';
import * as mockSleep from '../mocks/sleep';
import {flushPromises} from '../mocks/utils';
import {devices, events as mockZHEvents} from '../mocks/zigbeeHerdsman';
@@ -15,7 +15,7 @@ import * as zhc from 'zigbee-herdsman-converters';
import {Controller} from '../../lib/controller';
import * as settings from '../../lib/util/settings';
const mocksClear = [mockMQTT.publishAsync, devices.bulb.save, mockLogger.info];
const mocksClear = [mockMQTTPublishAsync, devices.bulb.save, mockLogger.info];
const DEFAULT_CONFIG: zhc.Ota.Settings = {
dataDir: data.mockDir,
@@ -25,8 +25,8 @@ const DEFAULT_CONFIG: zhc.Ota.Settings = {
describe('Extension: OTAUpdate', () => {
let controller: Controller;
const updateSpy = jest.spyOn(zhc.ota, 'update');
const isUpdateAvailableSpy = jest.spyOn(zhc.ota, 'isUpdateAvailable');
const updateSpy = vi.spyOn(zhc.ota, 'update');
const isUpdateAvailableSpy = vi.spyOn(zhc.ota, 'isUpdateAvailable');
const resetExtension = async (): Promise<void> => {
await controller.enableDisableExtension(false, 'OTAUpdate');
@@ -34,19 +34,19 @@ describe('Extension: OTAUpdate', () => {
};
beforeAll(async () => {
jest.useFakeTimers();
vi.useFakeTimers();
mockSleep.mock();
data.writeDefaultConfiguration();
settings.reRead();
settings.reRead();
controller = new Controller(jest.fn(), jest.fn());
controller = new Controller(vi.fn(), vi.fn());
await controller.start();
await flushPromises();
});
afterAll(async () => {
mockSleep.restore();
jest.useRealTimers();
vi.useRealTimers();
});
beforeEach(async () => {
@@ -111,21 +111,21 @@ describe('Extension: OTAUpdate', () => {
expect(devices.bulb.save).toHaveBeenCalledTimes(1);
expect(devices.bulb.endpoints[0].read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {sendPolicy: 'immediate'});
expect(devices.bulb.endpoints[0].read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {sendPolicy: undefined});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({update: {state: 'updating', progress: 0}}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({update: {state: 'updating', progress: 0}}), {
retain: true,
qos: 0,
});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bulb',
stringify({update: {state: 'updating', progress: 10, remaining: 3600}}),
{retain: true, qos: 0},
);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bulb',
stringify({update: {state: 'idle', installed_version: 90, latest_version: 90}}),
{retain: true, qos: 0},
);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/update',
stringify({
data: {
@@ -137,7 +137,7 @@ describe('Extension: OTAUpdate', () => {
}),
{retain: false, qos: 0},
);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0});
});
it('Should handle when OTA update fails', async () => {
@@ -149,8 +149,8 @@ describe('Extension: OTAUpdate', () => {
mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/ota_update/update', stringify({id: 'bulb'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({update: {state: 'available'}}), {retain: true, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({update: {state: 'available'}}), {retain: true, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/update',
stringify({data: {}, status: 'error', error: "Update of 'bulb' failed (Update failed)"}),
{retain: false, qos: 0},
@@ -164,20 +164,20 @@ describe('Extension: OTAUpdate', () => {
expect(isUpdateAvailableSpy).toHaveBeenCalledTimes(1);
expect(isUpdateAvailableSpy).toHaveBeenNthCalledWith(1, devices.bulb, {}, undefined, false);
expect(updateSpy).toHaveBeenCalledTimes(0);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/check',
stringify({data: {id: 'bulb', update_available: false}, status: 'ok'}),
{retain: false, qos: 0},
);
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
isUpdateAvailableSpy.mockResolvedValueOnce({available: true, currentFileVersion: 10, otaFileVersion: 12});
mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/ota_update/check', 'bulb');
await flushPromises();
expect(isUpdateAvailableSpy).toHaveBeenCalledTimes(2);
expect(isUpdateAvailableSpy).toHaveBeenNthCalledWith(2, devices.bulb, {}, undefined, false);
expect(updateSpy).toHaveBeenCalledTimes(0);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/check',
stringify({data: {id: 'bulb', update_available: true}, status: 'ok'}),
{retain: false, qos: 0},
@@ -188,7 +188,7 @@ describe('Extension: OTAUpdate', () => {
expect(isUpdateAvailableSpy).toHaveBeenCalledTimes(3);
expect(isUpdateAvailableSpy).toHaveBeenNthCalledWith(3, devices.bulb, {}, undefined, true);
expect(updateSpy).toHaveBeenCalledTimes(0);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/check',
stringify({data: {id: 'bulb', update_available: false}, status: 'ok'}),
{retain: false, qos: 0},
@@ -199,14 +199,14 @@ describe('Extension: OTAUpdate', () => {
const originalDefinition = device.definition;
device.definition = Object.assign({}, originalDefinition, {ota: {suppressElementImageParseFailure: true}});
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
isUpdateAvailableSpy.mockResolvedValueOnce({available: true, currentFileVersion: 10, otaFileVersion: 12});
mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/ota_update/check/downgrade', 'bulb');
await flushPromises();
expect(isUpdateAvailableSpy).toHaveBeenCalledTimes(4);
expect(isUpdateAvailableSpy).toHaveBeenNthCalledWith(4, devices.bulb, {suppressElementImageParseFailure: true}, undefined, true);
expect(updateSpy).toHaveBeenCalledTimes(0);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/check',
stringify({data: {id: 'bulb', update_available: true}, status: 'ok'}),
{retain: false, qos: 0},
@@ -222,7 +222,7 @@ describe('Extension: OTAUpdate', () => {
await flushPromises();
expect(isUpdateAvailableSpy).toHaveBeenCalledTimes(1);
expect(updateSpy).toHaveBeenCalledTimes(0);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/check',
stringify({
data: {},
@@ -236,7 +236,7 @@ describe('Extension: OTAUpdate', () => {
it('Should fail when device does not exist', async () => {
mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/ota_update/check', 'not_existing_deviceooo');
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/check',
stringify({data: {}, status: 'error', error: `Device 'not_existing_deviceooo' does not exist`}),
{retain: false, qos: 0},
@@ -246,7 +246,7 @@ describe('Extension: OTAUpdate', () => {
it('Should not check for OTA when device does not support it', async () => {
mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/ota_update/check', 'dimmer_wall_switch');
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/check',
stringify({data: {}, status: 'error', error: `Device 'dimmer_wall_switch' does not support OTA updates`}),
{retain: false, qos: 0},
@@ -267,9 +267,9 @@ describe('Extension: OTAUpdate', () => {
mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/ota_update/check', 'bulb');
await flushPromises();
expect(isUpdateAvailableSpy).toHaveBeenCalledTimes(1);
jest.runOnlyPendingTimers();
vi.runOnlyPendingTimers();
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/check',
stringify({data: {}, status: 'error', error: `Update or check for update already in progress for 'bulb'`}),
{retain: false, qos: 0},
@@ -278,11 +278,11 @@ describe('Extension: OTAUpdate', () => {
it('Shouldnt crash when read modelID before/after OTA update fails', async () => {
devices.bulb.endpoints[0].read.mockRejectedValueOnce('Failed from').mockRejectedValueOnce('Failed to');
updateSpy.mockImplementation();
updateSpy.mockImplementationOnce(vi.fn());
mockMQTTEvents.message('zigbee2mqtt/bridge/request/device/ota_update/update', 'bulb');
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bridge/response/device/ota_update/update',
stringify({data: {id: 'bulb', from: undefined, to: undefined}, status: 'ok'}),
{retain: false, qos: 0},
@@ -319,7 +319,7 @@ describe('Extension: OTAUpdate', () => {
await mockZHEvents.message(payload);
await flushPromises();
expect(mockLogger.info).not.toHaveBeenCalledWith(`Update available for 'bulb'`);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bulb',
stringify({update: {state: 'available', installed_version: 10, latest_version: 12}}),
{retain: true, qos: 0},
@@ -344,7 +344,7 @@ describe('Extension: OTAUpdate', () => {
expect(isUpdateAvailableSpy).toHaveBeenCalledWith(devices.bulb, {}, {imageType: 12382}, false);
expect(devices.bulb.endpoints[0].commandResponse).toHaveBeenCalledTimes(1);
expect(devices.bulb.endpoints[0].commandResponse).toHaveBeenCalledWith('genOta', 'queryNextImageResponse', {status: 0x98}, undefined, 10);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({update: {state: 'idle'}}), {retain: true, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bulb', stringify({update: {state: 'idle'}}), {retain: true, qos: 0});
});
it('Should check for update when device requests it and it is not available', async () => {
@@ -365,7 +365,7 @@ describe('Extension: OTAUpdate', () => {
expect(isUpdateAvailableSpy).toHaveBeenCalledWith(devices.bulb, {}, {imageType: 12382}, false);
expect(devices.bulb.endpoints[0].commandResponse).toHaveBeenCalledTimes(1);
expect(devices.bulb.endpoints[0].commandResponse).toHaveBeenCalledWith('genOta', 'queryNextImageResponse', {status: 0x98}, undefined, 10);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bulb',
stringify({update: {state: 'idle', installed_version: 13, latest_version: 13}}),
{retain: true, qos: 0},
@@ -427,7 +427,7 @@ describe('Extension: OTAUpdate', () => {
});
it('Sets given configuration', async () => {
const setConfiguration = jest.spyOn(zhc.ota, 'setConfiguration');
const setConfiguration = vi.spyOn(zhc.ota, 'setConfiguration');
settings.set(['ota', 'zigbee_ota_override_index_location'], 'local.index.json');
settings.set(['ota', 'image_block_response_delay'], 10000);
settings.set(['ota', 'default_maximum_data_size'], 10);
+194 -194
View File
@@ -1,6 +1,6 @@
import * as data from '../mocks/data';
import {mockLogger} from '../mocks/logger';
import {mockMQTT, events as mockMQTTEvents} from '../mocks/mqtt';
import {events as mockMQTTEvents, mockMQTTPublishAsync} from '../mocks/mqtt';
import * as mockSleep from '../mocks/sleep';
import {flushPromises} from '../mocks/utils';
import {devices, groups, events as mockZHEvents} from '../mocks/zigbeeHerdsman';
@@ -13,7 +13,7 @@ import {Controller} from '../../lib/controller';
import {loadTopicGetSetRegex} from '../../lib/extension/publish';
import * as settings from '../../lib/util/settings';
const mocksClear = [mockMQTT.publishAsync, mockLogger.warning, mockLogger.debug];
const mocksClear = [mockMQTTPublishAsync, mockLogger.warning, mockLogger.debug];
describe('Extension: Publish', () => {
let controller: Controller;
@@ -32,9 +32,9 @@ describe('Extension: Publish', () => {
};
beforeAll(async () => {
jest.useFakeTimers();
vi.useFakeTimers();
data.writeEmptyState();
controller = new Controller(jest.fn(), jest.fn());
controller = new Controller(vi.fn(), vi.fn());
mockSleep.mock();
await controller.start();
await flushPromises();
@@ -62,8 +62,8 @@ describe('Extension: Publish', () => {
});
afterAll(async () => {
await jest.runOnlyPendingTimersAsync();
jest.useRealTimers();
await vi.runOnlyPendingTimersAsync();
vi.useRealTimers();
mockSleep.restore();
});
@@ -73,10 +73,10 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('genLevelCtrl', 'moveToLevelWithOnOff', {level: 200, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({brightness: 200, state: 'ON'});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({brightness: 200, state: 'ON'});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should corretly handle mallformed messages', async () => {
@@ -106,10 +106,10 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('genOnOff', 'on', {}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/wohnzimmer.light.wall.right');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({state: 'ON'});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/wohnzimmer.light.wall.right');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({state: 'ON'});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should publish messages to zigbee devices when brightness is in %', async () => {
@@ -119,10 +119,10 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('genLevelCtrl', 'moveToLevelWithOnOff', {level: 235, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({state: 'ON', brightness: 235});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({state: 'ON', brightness: 235});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should publish messages to zigbee devices when brightness is in number', async () => {
@@ -132,10 +132,10 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('genLevelCtrl', 'moveToLevelWithOnOff', {level: 230, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({state: 'ON', brightness: 230});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({state: 'ON', brightness: 230});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should publish messages to zigbee devices with color_temp', async () => {
@@ -145,10 +145,10 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('lightingColorCtrl', 'moveToColorTemp', {colortemp: 222, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({color_mode: 'color_temp', color_temp: 222});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({color_mode: 'color_temp', color_temp: 222});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should publish messages to zigbee devices with color_temp in %', async () => {
@@ -158,10 +158,10 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('lightingColorCtrl', 'moveToColorTemp', {colortemp: 500, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({color_mode: 'color_temp', color_temp: 500});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({color_mode: 'color_temp', color_temp: 500});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should publish messages to zigbee devices with non-default ep', async () => {
@@ -171,10 +171,10 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('genOnOff', 'off', {}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/wall_switch');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({state: 'OFF'});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/wall_switch');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({state: 'OFF'});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should publish messages to zigbee devices with non-default ep and postfix', async () => {
@@ -184,10 +184,10 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('genOnOff', 'off', {}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/wall_switch_double');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[1][1])).toStrictEqual({state_right: 'OFF'});
expect(mockMQTT.publishAsync.mock.calls[1][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/wall_switch_double');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[1][1])).toStrictEqual({state_right: 'OFF'});
expect(mockMQTTPublishAsync.mock.calls[1][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should publish messages to zigbee devices with endpoint ID', async () => {
@@ -197,7 +197,7 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('genOnOff', 'off', {}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/wall_switch_double', stringify({state_right: 'OFF'}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/wall_switch_double', stringify({state_right: 'OFF'}), {
qos: 0,
retain: false,
});
@@ -210,10 +210,10 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('genOnOff', 'off', {}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/wall_switch_double');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({state_right: 'OFF'});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/wall_switch_double');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({state_right: 'OFF'});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should publish messages to zigbee devices to non default-ep with brightness_[EP]', async () => {
@@ -223,7 +223,7 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('genLevelCtrl', 'moveToLevelWithOnOff', {level: 50, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/QS-Zigbee-D02-TRIAC-2C-LN', stringify({brightness_l2: 50, state_l2: 'ON'}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/QS-Zigbee-D02-TRIAC-2C-LN', stringify({brightness_l2: 50, state_l2: 'ON'}), {
retain: false,
qos: 0,
});
@@ -241,7 +241,7 @@ describe('Extension: Publish', () => {
{dpValues: [{data: [1], datatype: 1, dp: 2}], seq: expect.any(Number)},
{disableDefaultResponse: true},
);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/TS0601_switch', stringify({state_l2: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/TS0601_switch', stringify({state_l2: 'ON'}), {retain: false, qos: 0});
});
it('Should publish messages to Tuya cover switch with dummy endpoints', async () => {
@@ -270,7 +270,7 @@ describe('Extension: Publish', () => {
{dpValues: [{data: [0], datatype: 1, dp: 101}], seq: expect.any(Number)},
{disableDefaultResponse: true},
);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/TS0601_cover_switch',
stringify({state_l2: 'OFF', state_l1: 'ON', state: 'OPEN'}),
{retain: false, qos: 0},
@@ -284,10 +284,10 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('lightingColorCtrl', 'moveToColor', {colorx: 6553500, colory: 3276750, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({color_mode: 'xy', color: {x: 100, y: 50}});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({color_mode: 'xy', color: {x: 100, y: 50}});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should publish messages to zigbee devices with color xy and state', async () => {
@@ -298,8 +298,8 @@ describe('Extension: Publish', () => {
expect(endpoint.command).toHaveBeenCalledTimes(2);
expect(endpoint.command).toHaveBeenCalledWith('genOnOff', 'on', {}, {});
expect(endpoint.command).toHaveBeenCalledWith('lightingColorCtrl', 'moveToColor', {colorx: 6553500, colory: 3276750, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bulb_color',
stringify({color_mode: 'xy', color: {x: 100, y: 50}, state: 'ON'}),
{retain: false, qos: 0},
@@ -314,8 +314,8 @@ describe('Extension: Publish', () => {
expect(endpoint.command).toHaveBeenCalledTimes(2);
expect(endpoint.command).toHaveBeenCalledWith('genLevelCtrl', 'moveToLevelWithOnOff', {level: 20, transtime: 0}, {});
expect(endpoint.command).toHaveBeenCalledWith('lightingColorCtrl', 'moveToColor', {colorx: 6553500, colory: 3276750, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bulb_color',
stringify({color_mode: 'xy', color: {x: 100, y: 50}, state: 'ON', brightness: 20}),
{retain: false, qos: 0},
@@ -330,8 +330,8 @@ describe('Extension: Publish', () => {
expect(endpoint.command).toHaveBeenCalledTimes(2);
expect(endpoint.command).toHaveBeenCalledWith('genLevelCtrl', 'moveToLevelWithOnOff', {level: 20, transtime: 0}, {});
expect(endpoint.command).toHaveBeenCalledWith('lightingColorCtrl', 'moveToColor', {colorx: 6553500, colory: 3276750, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bulb_color',
stringify({color: {x: 100, y: 50}, state: 'ON', brightness: 20, color_mode: 'xy'}),
{retain: false, qos: 0},
@@ -346,8 +346,8 @@ describe('Extension: Publish', () => {
expect(endpoint.command).toHaveBeenCalledTimes(2);
expect(endpoint.command).toHaveBeenNthCalledWith(1, 'lightingColorCtrl', 'moveToColor', {colorx: 6553500, colory: 3276750, transtime: 0}, {});
expect(endpoint.command).toHaveBeenNthCalledWith(2, 'genOnOff', 'off', {}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bulb_color',
stringify({color_mode: 'xy', color: {x: 100, y: 50}, state: 'OFF'}),
{retain: false, qos: 0},
@@ -361,9 +361,9 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('lightingColorCtrl', 'moveToColor', {colorx: 17806, colory: 43155, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({color: {x: 0.2717, y: 0.6585}, color_mode: 'xy'});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({color: {x: 0.2717, y: 0.6585}, color_mode: 'xy'});
});
it('Should publish messages to zigbee devices with color rgb string', async () => {
@@ -373,9 +373,9 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('lightingColorCtrl', 'moveToColor', {colorx: 17806, colory: 43155, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({color: {x: 0.2717, y: 0.6585}, color_mode: 'xy'});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({color: {x: 0.2717, y: 0.6585}, color_mode: 'xy'});
});
it('Should publish messages to zigbee devices with brightness', async () => {
@@ -385,9 +385,9 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('genLevelCtrl', 'moveToLevelWithOnOff', {level: 50, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({state: 'ON', brightness: 50});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({state: 'ON', brightness: 50});
});
it('Should publish messages groups', async () => {
@@ -397,9 +397,9 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(group.command).toHaveBeenCalledTimes(1);
expect(group.command).toHaveBeenCalledWith('genOnOff', 'on', {}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); // 'zigbee2mqtt/bulb_color' + below
expect(mockMQTT.publishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/group_1');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[1][1])).toStrictEqual({state: 'ON'});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2); // 'zigbee2mqtt/bulb_color' + below
expect(mockMQTTPublishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/group_1');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[1][1])).toStrictEqual({state: 'ON'});
group.members.pop();
});
@@ -410,9 +410,9 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(group.command).toHaveBeenCalledTimes(1);
expect(group.command).toHaveBeenCalledWith('genLevelCtrl', 'moveToLevelWithOnOff', {level: 128, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); // 'zigbee2mqtt/bulb_color' + below
expect(mockMQTT.publishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/group_1');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[1][1])).toStrictEqual({state: 'ON', brightness: 128});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2); // 'zigbee2mqtt/bulb_color' + below
expect(mockMQTTPublishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/group_1');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[1][1])).toStrictEqual({state: 'ON', brightness: 128});
group.members.pop();
});
@@ -437,9 +437,9 @@ describe('Extension: Publish', () => {
console.log(mockLogger.warning.mock.calls);
expect(group.command).toHaveBeenCalledTimes(1);
expect(group.command).toHaveBeenCalledWith('genOnOff', 'on', {}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/hue_twilight_group');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[1][1])).toStrictEqual({state: 'ON'});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/hue_twilight_group');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[1][1])).toStrictEqual({state: 'ON'});
});
it('Should publish messages to groups with on and brightness', async () => {
@@ -449,9 +449,9 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(group.command).toHaveBeenCalledTimes(1);
expect(group.command).toHaveBeenCalledWith('genLevelCtrl', 'moveToLevelWithOnOff', {level: 50, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); // 'zigbee2mqtt/bulb_color' + below
expect(mockMQTT.publishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/group_1');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[1][1])).toStrictEqual({state: 'ON', brightness: 50});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2); // 'zigbee2mqtt/bulb_color' + below
expect(mockMQTTPublishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/group_1');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[1][1])).toStrictEqual({state: 'ON', brightness: 50});
group.members.pop();
});
@@ -462,9 +462,9 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(group.command).toHaveBeenCalledTimes(1);
expect(group.command).toHaveBeenCalledWith('genOnOff', 'off', {}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); // 'zigbee2mqtt/bulb_color' + below
expect(mockMQTT.publishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/group_1');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[1][1])).toStrictEqual({state: 'OFF'});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2); // 'zigbee2mqtt/bulb_color' + below
expect(mockMQTTPublishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/group_1');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[1][1])).toStrictEqual({state: 'OFF'});
group.members.pop();
});
@@ -475,9 +475,9 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(group.command).toHaveBeenCalledTimes(1);
expect(group.command).toHaveBeenCalledWith('lightingColorCtrl', 'moveToColor', {colorx: 24248, colory: 18350, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); // 'zigbee2mqtt/bulb_color' + below
expect(mockMQTT.publishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/group_1');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[1][1])).toStrictEqual({color: {x: 0.37, y: 0.28}, color_mode: 'xy'});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2); // 'zigbee2mqtt/bulb_color' + below
expect(mockMQTTPublishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/group_1');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[1][1])).toStrictEqual({color: {x: 0.37, y: 0.28}, color_mode: 'xy'});
group.members.pop();
});
@@ -488,9 +488,9 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(group.command).toHaveBeenCalledTimes(1);
expect(group.command).toHaveBeenCalledWith('lightingColorCtrl', 'moveToColorTemp', {colortemp: 100, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); // 'zigbee2mqtt/bulb_color' + below
expect(mockMQTT.publishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/group_1');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[1][1])).toStrictEqual({color_temp: 100, color_mode: 'color_temp'});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2); // 'zigbee2mqtt/bulb_color' + below
expect(mockMQTTPublishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/group_1');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[1][1])).toStrictEqual({color_temp: 100, color_mode: 'color_temp'});
group.members.pop();
});
@@ -515,7 +515,7 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('genLevelCtrl', 'moveToLevelWithOnOff', {level: 200, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(0);
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(0);
});
it('Shouldnt publish new brightness state when filtered_optimistic is used', async () => {
@@ -526,15 +526,15 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('genLevelCtrl', 'moveToLevelWithOnOff', {level: 200, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({state: 'ON'});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({state: 'ON'});
});
it('Shouldnt publish new state when optimistic = false for group', async () => {
settings.set(['groups', '2', 'optimistic'], false);
await mockMQTTEvents.message('zigbee2mqtt/group_2/set', stringify({brightness: '200'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(0);
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(0);
});
it('Should handle non-valid topics', async () => {
@@ -681,9 +681,9 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('lightingColorCtrl', 'moveToColor', {colorx: 17806, colory: 43155, transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({color: {x: 0.2717, y: 0.6585}, color_mode: 'xy'});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({color: {x: 0.2717, y: 0.6585}, color_mode: 'xy'});
});
it('Should parse set with ieeeAddr topic', async () => {
@@ -730,13 +730,13 @@ describe('Extension: Publish', () => {
expect(endpoint.command).toHaveBeenCalledTimes(2);
expect(endpoint.command).toHaveBeenCalledWith('genOnOff', 'on', {}, {});
expect(endpoint.command).toHaveBeenCalledWith('genOnOff', 'toggle', {}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync.mock.calls[0]).toEqual([
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync.mock.calls[0]).toEqual([
'zigbee2mqtt/wall_switch_double',
stringify({state_left: 'ON'}),
{qos: 0, retain: false},
]);
expect(mockMQTT.publishAsync.mock.calls[1]).toEqual([
expect(mockMQTTPublishAsync.mock.calls[1]).toEqual([
'zigbee2mqtt/wall_switch_double',
stringify({state_left: 'OFF'}),
{qos: 0, retain: false},
@@ -803,25 +803,25 @@ describe('Extension: Publish', () => {
it('Should also use on/off cluster when controlling group with switch', async () => {
const group = groups.group_with_switch;
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
group.command.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/switch_group/set', stringify({state: 'ON', brightness: 100}));
await flushPromises();
expect(group.command).toHaveBeenCalledTimes(2);
expect(group.command).toHaveBeenCalledWith('genLevelCtrl', 'moveToLevelWithOnOff', {level: 100, transtime: 0}, {});
expect(group.command).toHaveBeenCalledWith('genOnOff', 'on', {}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/switch_group', stringify({state: 'ON', brightness: 100}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/switch_group', stringify({state: 'ON', brightness: 100}), {
retain: false,
qos: 0,
});
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
group.command.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/switch_group/set', stringify({state: 'OFF'}));
await flushPromises();
expect(group.command).toHaveBeenCalledTimes(1);
expect(group.command).toHaveBeenCalledWith('genOnOff', 'off', {}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/switch_group', stringify({state: 'OFF', brightness: 100}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/switch_group', stringify({state: 'OFF', brightness: 100}), {
retain: false,
qos: 0,
});
@@ -835,9 +835,9 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(group.command).toHaveBeenCalledTimes(1);
expect(group.command).toHaveBeenCalledWith('genLevelCtrl', 'moveToLevelWithOnOff', {level: 100, transtime: 200}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2); // zigbee2mqtt/bulb_color + below
expect(mockMQTT.publishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/group_1');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[1][1])).toStrictEqual({state: 'ON', brightness: 100});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2); // zigbee2mqtt/bulb_color + below
expect(mockMQTTPublishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/group_1');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[1][1])).toStrictEqual({state: 'ON', brightness: 100});
group.members.pop();
});
@@ -885,24 +885,24 @@ describe('Extension: Publish', () => {
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON', transition: 0}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(5);
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(1, 'zigbee2mqtt/bulb_color', stringify({state: 'ON', brightness: 200}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(5);
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(1, 'zigbee2mqtt/bulb_color', stringify({state: 'ON', brightness: 200}), {
retain: false,
qos: 0,
});
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(2, 'zigbee2mqtt/bulb_color', stringify({state: 'OFF', brightness: 200}), {
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(2, 'zigbee2mqtt/bulb_color', stringify({state: 'OFF', brightness: 200}), {
retain: false,
qos: 0,
});
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(3, 'zigbee2mqtt/bulb_color', stringify({state: 'OFF', brightness: 1}), {
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(3, 'zigbee2mqtt/bulb_color', stringify({state: 'OFF', brightness: 1}), {
retain: false,
qos: 0,
});
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(4, 'zigbee2mqtt/bulb_color', stringify({state: 'OFF', brightness: 1}), {
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(4, 'zigbee2mqtt/bulb_color', stringify({state: 'OFF', brightness: 1}), {
retain: false,
qos: 0,
});
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(5, 'zigbee2mqtt/bulb_color', stringify({state: 'ON', brightness: 200}), {
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(5, 'zigbee2mqtt/bulb_color', stringify({state: 'ON', brightness: 200}), {
retain: false,
qos: 0,
});
@@ -922,8 +922,8 @@ describe('Extension: Publish', () => {
await flushPromises();
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'ON', transition: 0.5}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(1, 'zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(2, 'zigbee2mqtt/bulb_color', stringify({state: 'ON', brightness: 254}), {
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(1, 'zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(2, 'zigbee2mqtt/bulb_color', stringify({state: 'ON', brightness: 254}), {
retain: false,
qos: 0,
});
@@ -1014,18 +1014,18 @@ describe('Extension: Publish', () => {
expect(endpoint.command.mock.calls[0]).toEqual(['genLevelCtrl', 'moveToLevelWithOnOff', {level: 50, transtime: 0}, {}]);
expect(endpoint.command.mock.calls[1]).toEqual(['genLevelCtrl', 'moveToLevelWithOnOff', {level: 0, transtime: 0}, {}]);
expect(endpoint.command.mock.calls[2]).toEqual(['genLevelCtrl', 'moveToLevelWithOnOff', {level: 50, transtime: 0}, {}]);
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTT.publishAsync.mock.calls[0]).toEqual([
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTTPublishAsync.mock.calls[0]).toEqual([
'zigbee2mqtt/bulb_color',
stringify({state: 'ON', brightness: 50}),
{qos: 0, retain: false},
]);
expect(mockMQTT.publishAsync.mock.calls[1]).toEqual([
expect(mockMQTTPublishAsync.mock.calls[1]).toEqual([
'zigbee2mqtt/bulb_color',
stringify({state: 'OFF', brightness: 0}),
{qos: 0, retain: false},
]);
expect(mockMQTT.publishAsync.mock.calls[2]).toEqual([
expect(mockMQTTPublishAsync.mock.calls[2]).toEqual([
'zigbee2mqtt/bulb_color',
stringify({state: 'ON', brightness: 50}),
{qos: 0, retain: false},
@@ -1045,18 +1045,18 @@ describe('Extension: Publish', () => {
expect(endpoint.command.mock.calls[0]).toEqual(['genLevelCtrl', 'moveToLevelWithOnOff', {level: 50, transtime: 0}, {}]);
expect(endpoint.command.mock.calls[1]).toEqual(['genLevelCtrl', 'moveToLevelWithOnOff', {level: 0, transtime: 30}, {}]);
expect(endpoint.command.mock.calls[2]).toEqual(['genLevelCtrl', 'moveToLevelWithOnOff', {level: 50, transtime: 0}, {}]);
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTT.publishAsync.mock.calls[0]).toEqual([
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTTPublishAsync.mock.calls[0]).toEqual([
'zigbee2mqtt/bulb_color',
stringify({state: 'ON', brightness: 50}),
{qos: 0, retain: false},
]);
expect(mockMQTT.publishAsync.mock.calls[1]).toEqual([
expect(mockMQTTPublishAsync.mock.calls[1]).toEqual([
'zigbee2mqtt/bulb_color',
stringify({state: 'OFF', brightness: 0}),
{qos: 0, retain: false},
]);
expect(mockMQTT.publishAsync.mock.calls[2]).toEqual([
expect(mockMQTTPublishAsync.mock.calls[2]).toEqual([
'zigbee2mqtt/bulb_color',
stringify({state: 'ON', brightness: 50}),
{qos: 0, retain: false},
@@ -1076,9 +1076,9 @@ describe('Extension: Publish', () => {
{direction: 0, enhancehue: 45510, saturation: 127, transtime: 0},
{},
]);
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({color: {hue: 250, saturation: 50}, color_mode: 'hs'});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({color: {hue: 250, saturation: 50}, color_mode: 'hs'});
});
it('ZNCLDJ11LM open', async () => {
@@ -1149,9 +1149,9 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command.mock.calls[0]).toEqual(['genLevelCtrl', 'moveToLevelWithOnOff', {level: 0, transtime: 10}, {}]);
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({state: 'OFF'});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({state: 'OFF'});
});
it('When device is turned off and on with transition with report enabled it should restore correct brightness', async () => {
@@ -1161,15 +1161,15 @@ describe('Extension: Publish', () => {
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({brightness: 200}));
await flushPromises();
endpoint.command.mockClear();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
// Turn off
await mockMQTTEvents.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(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0]).toEqual([
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0]).toEqual([
'zigbee2mqtt/bulb_color',
stringify({state: 'OFF', brightness: 200}),
{qos: 0, retain: false},
@@ -1185,8 +1185,8 @@ describe('Extension: Publish', () => {
linkquality: 10,
});
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync.mock.calls[1]).toEqual([
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync.mock.calls[1]).toEqual([
'zigbee2mqtt/bulb_color',
stringify({state: 'OFF', brightness: 1}),
{qos: 0, retain: false},
@@ -1197,8 +1197,8 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(2);
expect(endpoint.command.mock.calls[1]).toEqual(['genLevelCtrl', 'moveToLevelWithOnOff', {level: 200, transtime: 30}, {}]);
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTT.publishAsync.mock.calls[2]).toEqual([
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTTPublishAsync.mock.calls[2]).toEqual([
'zigbee2mqtt/bulb_color',
stringify({state: 'ON', brightness: 200}),
{qos: 0, retain: false},
@@ -1212,15 +1212,15 @@ describe('Extension: Publish', () => {
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({brightness: 200}));
await flushPromises();
endpoint.command.mockClear();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
// Turn off
await mockMQTTEvents.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(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0]).toEqual([
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0]).toEqual([
'zigbee2mqtt/bulb_color',
stringify({state: 'OFF', brightness: 200}),
{qos: 0, retain: false},
@@ -1231,8 +1231,8 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(2);
expect(endpoint.command.mock.calls[1]).toEqual(['genLevelCtrl', 'moveToLevelWithOnOff', {level: 200, transtime: 0}, {}]);
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync.mock.calls[1]).toEqual([
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync.mock.calls[1]).toEqual([
'zigbee2mqtt/bulb_color',
stringify({state: 'ON', brightness: 200}),
{qos: 0, retain: false},
@@ -1248,9 +1248,9 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command.mock.calls[0]).toEqual(['genOnOff', 'on', {}, {}]);
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({state: 'ON'});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({state: 'ON'});
});
it('Home Assistant: should not set state when color temperature is also set and device is already on', async () => {
@@ -1267,9 +1267,9 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command.mock.calls[0]).toEqual(['lightingColorCtrl', 'moveToColorTemp', {colortemp: 100, transtime: 0}, {}]);
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({state: 'ON', color_temp: 100, color_mode: 'color_temp'});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({state: 'ON', color_temp: 100, color_mode: 'color_temp'});
});
it('Home Assistant: should set state when color temperature is also set and device is off', async () => {
@@ -1287,8 +1287,8 @@ describe('Extension: Publish', () => {
expect(endpoint.command).toHaveBeenCalledTimes(2);
expect(endpoint.command.mock.calls[0]).toEqual(['genOnOff', 'on', {}, {}]);
expect(endpoint.command.mock.calls[1]).toEqual(['lightingColorCtrl', 'moveToColorTemp', {colortemp: 100, transtime: 0}, {}]);
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
'zigbee2mqtt/bulb_color',
stringify({state: 'ON', color_temp: 100, color_mode: 'color_temp'}),
{retain: false, qos: 0},
@@ -1309,9 +1309,9 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command.mock.calls[0]).toEqual(['lightingColorCtrl', 'moveToColor', {colorx: 26869, colory: 16384, transtime: 0}, {}]);
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({color: {x: 0.41, y: 0.25}, state: 'ON', color_mode: 'xy'});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({color: {x: 0.41, y: 0.25}, state: 'ON', color_mode: 'xy'});
});
it('Should publish correct state on toggle command to zigbee bulb', async () => {
@@ -1322,13 +1322,13 @@ describe('Extension: Publish', () => {
// At this point the bulb has no state yet, so we cannot determine the next state and therefore shouldn't publish it to mockMQTT.
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('genOnOff', 'toggle', {}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(0);
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(0);
// Turn bulb off so that the bulb gets a state.
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'OFF'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(1, 'zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(1, 'zigbee2mqtt/bulb_color', stringify({state: 'OFF'}), {retain: false, qos: 0});
// Toggle again, now that we have state it should publish state ON
endpoint.read.mockImplementationOnce((cluster, attrs) => {
@@ -1339,8 +1339,8 @@ describe('Extension: Publish', () => {
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({state: 'TOGGLE'}));
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(3);
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(2, 'zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(2, 'zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {retain: false, qos: 0});
});
it('Should publish messages with options disableDefaultResponse', async () => {
@@ -1350,20 +1350,20 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('genOnOff', 'off', {}, {disableDefaultResponse: true});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/led_controller_1');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({state: 'OFF'});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/led_controller_1');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({state: 'OFF'});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should publish messages to zigbee devices', async () => {
settings.set(['advanced', 'last_seen'], 'ISO_8601');
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({brightness: '200'}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(typeof JSON.parse(mockMQTT.publishAsync.mock.calls[0][1]).last_seen).toStrictEqual('string');
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bulb_color');
expect(typeof JSON.parse(mockMQTTPublishAsync.mock.calls[0][1]).last_seen).toStrictEqual('string');
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should publish brightness_move up to zigbee devices', async () => {
@@ -1451,10 +1451,10 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(endpoint.command).toHaveBeenCalledTimes(1);
expect(endpoint.command).toHaveBeenCalledWith('genLevelCtrl', 'moveToLevelWithOnOff', {level: '255', transtime: 0}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/roller_shutter');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({position: 100});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/roller_shutter');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({position: 100});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should publish to MKS-CM-W5', async () => {
@@ -1469,10 +1469,10 @@ describe('Extension: Publish', () => {
{dpValues: [{data: [1], datatype: 1, dp: 3}], seq: expect.any(Number)},
{disableDefaultResponse: true},
);
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/MKS-CM-W5');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({state_l3: 'ON'});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/MKS-CM-W5');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({state_l3: 'ON'});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should publish separate genOnOff to GL-S-007ZS when setting state and brightness as bulb doesnt turn on with moveToLevelWithOnOff', async () => {
@@ -1481,11 +1481,11 @@ describe('Extension: Publish', () => {
const endpoint = device.getEndpoint(1)!;
await mockMQTTEvents.message('zigbee2mqtt/GL-S-007ZS/set', stringify({state: 'ON', brightness: 20}));
await flushPromises();
await jest.runOnlyPendingTimersAsync();
await vi.runOnlyPendingTimersAsync();
expect(endpoint.command).toHaveBeenCalledTimes(2);
expect(endpoint.command.mock.calls[0]).toEqual(['genOnOff', 'on', {}, {}]);
expect(endpoint.command.mock.calls[1]).toEqual(['genLevelCtrl', 'moveToLevelWithOnOff', {level: 20, transtime: 0}, {}]);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/GL-S-007ZS', stringify({state: 'ON', brightness: 20}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/GL-S-007ZS', stringify({state: 'ON', brightness: 20}), {
qos: 0,
retain: false,
});
@@ -1568,19 +1568,19 @@ describe('Extension: Publish', () => {
expect(endpoint.command.mock.calls[2]).toEqual(['genOnOff', 'off', {}, {}]);
expect(endpoint.command.mock.calls[3]).toEqual(['genOnOff', 'on', {}, {}]);
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(4);
expect(mockMQTT.publishAsync.mock.calls[0]).toEqual(['zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {qos: 0, retain: false}]);
expect(mockMQTT.publishAsync.mock.calls[1]).toEqual([
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(4);
expect(mockMQTTPublishAsync.mock.calls[0]).toEqual(['zigbee2mqtt/bulb_color', stringify({state: 'ON'}), {qos: 0, retain: false}]);
expect(mockMQTTPublishAsync.mock.calls[1]).toEqual([
'zigbee2mqtt/bulb_color',
stringify({state: 'ON', brightness: 150}),
{qos: 0, retain: false},
]);
expect(mockMQTT.publishAsync.mock.calls[2]).toEqual([
expect(mockMQTTPublishAsync.mock.calls[2]).toEqual([
'zigbee2mqtt/bulb_color',
stringify({state: 'OFF', brightness: 150}),
{qos: 0, retain: false},
]);
expect(mockMQTT.publishAsync.mock.calls[3]).toEqual([
expect(mockMQTTPublishAsync.mock.calls[3]).toEqual([
'zigbee2mqtt/bulb_color',
stringify({state: 'ON', brightness: 150}),
{qos: 0, retain: false},
@@ -1597,63 +1597,63 @@ describe('Extension: Publish', () => {
await flushPromises();
expect(group.command).toHaveBeenCalledTimes(1);
expect(group.command).toHaveBeenCalledWith('genScenes', 'store', {groupid: 15071, sceneid: 1}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), {retain: true, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/bridge/groups', expect.any(String), {retain: true, qos: 0});
await mockMQTTEvents.message('zigbee2mqtt/bulb_color_2/set', stringify({state: 'ON', brightness: 250, color_temp: 20}));
await mockMQTTEvents.message('zigbee2mqtt/bulb_2/set', stringify({state: 'ON', brightness: 110}));
await flushPromises();
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
group.command.mockClear();
await mockMQTTEvents.message('zigbee2mqtt/group_tradfri_remote/set', stringify({scene_recall: 1}));
await flushPromises();
expect(group.command).toHaveBeenCalledTimes(1);
expect(group.command).toHaveBeenCalledWith('genScenes', 'recall', {groupid: 15071, sceneid: 1}, {});
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(8);
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(8);
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(
1,
'zigbee2mqtt/group_tradfri_remote',
stringify({brightness: 50, color_temp: 290, state: 'ON', color_mode: 'color_temp'}),
{retain: false, qos: 0},
);
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(
2,
'zigbee2mqtt/bulb_color_2',
stringify({color_mode: 'color_temp', brightness: 50, color_temp: 290, state: 'ON'}),
{retain: false, qos: 0},
);
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(
3,
'zigbee2mqtt/ha_discovery_group',
stringify({brightness: 50, color_mode: 'color_temp', color_temp: 290, state: 'ON'}),
{retain: false, qos: 0},
);
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(
4,
'zigbee2mqtt/group_tradfri_remote',
stringify({brightness: 100, color_temp: 290, state: 'ON', color_mode: 'color_temp'}),
{retain: false, qos: 0},
);
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(
5,
'zigbee2mqtt/bulb_2',
stringify({brightness: 100, color_mode: 'color_temp', color_temp: 290, state: 'ON'}),
{retain: false, qos: 0},
);
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(
6,
'zigbee2mqtt/group_with_tradfri',
stringify({brightness: 100, color_mode: 'color_temp', color_temp: 290, state: 'ON'}),
{retain: false, qos: 0},
);
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(
7,
'zigbee2mqtt/switch_group',
stringify({brightness: 100, color_mode: 'color_temp', color_temp: 290, state: 'ON'}),
{retain: false, qos: 0},
);
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(
8,
'zigbee2mqtt/ha_discovery_group',
stringify({brightness: 100, color_mode: 'color_temp', color_temp: 290, state: 'ON'}),
@@ -1668,18 +1668,18 @@ describe('Extension: Publish', () => {
await flushPromises();
await mockMQTTEvents.message('zigbee2mqtt/bulb_color/set', stringify({color_temp: 300}));
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(1, 'zigbee2mqtt/bulb_color', stringify({color_mode: 'color_temp', color_temp: 100}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(1, 'zigbee2mqtt/bulb_color', stringify({color_mode: 'color_temp', color_temp: 100}), {
retain: false,
qos: 0,
});
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(
2,
'zigbee2mqtt/bulb_color',
stringify({color: {x: 0.1, y: 0.5}, color_mode: 'xy', color_temp: 79}),
{retain: false, qos: 0},
);
expect(mockMQTT.publishAsync).toHaveBeenNthCalledWith(
expect(mockMQTTPublishAsync).toHaveBeenNthCalledWith(
3,
'zigbee2mqtt/bulb_color',
stringify({color: {x: 0.4152, y: 0.3954}, color_mode: 'color_temp', color_temp: 300}),
+139 -142
View File
@@ -1,6 +1,6 @@
import * as data from '../mocks/data';
import {mockLogger} from '../mocks/logger';
import {mockMQTT, events as mockMQTTEvents} from '../mocks/mqtt';
import {events as mockMQTTEvents, mockMQTTPublishAsync} from '../mocks/mqtt';
import * as mockSleep from '../mocks/sleep';
import {flushPromises} from '../mocks/utils';
import {devices, events as mockZHEvents} from '../mocks/zigbeeHerdsman';
@@ -10,17 +10,17 @@ import stringify from 'json-stable-stringify-without-jsonify';
import {Controller} from '../../lib/controller';
import * as settings from '../../lib/util/settings';
const mocksClear = [mockMQTT.publishAsync, mockLogger.warning, mockLogger.debug];
const mocksClear = [mockMQTTPublishAsync, mockLogger.warning, mockLogger.debug];
describe('Extension: Receive', () => {
let controller: Controller;
beforeAll(async () => {
jest.useFakeTimers();
controller = new Controller(jest.fn(), jest.fn());
vi.useFakeTimers();
controller = new Controller(vi.fn(), vi.fn());
mockSleep.mock();
await controller.start();
await jest.runOnlyPendingTimersAsync();
await vi.runOnlyPendingTimersAsync();
});
beforeEach(async () => {
@@ -33,7 +33,7 @@ describe('Extension: Receive', () => {
});
afterAll(async () => {
jest.useRealTimers();
vi.useRealTimers();
mockSleep.restore();
});
@@ -44,8 +44,8 @@ describe('Extension: Receive', () => {
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/button', stringify({action: 'single', linkquality: 10}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/button', stringify({action: 'single', linkquality: 10}), {
retain: false,
qos: 0,
});
@@ -57,10 +57,10 @@ describe('Extension: Receive', () => {
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({action: 'single_left'});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({action: 'single_left'});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should handle a zigbee message which uses ep (right)', async () => {
@@ -69,10 +69,10 @@ describe('Extension: Receive', () => {
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(2), type: 'attributeReport', linkquality: 10};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({action: 'single_right'});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({action: 'single_right'});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should handle a zigbee message with default precision', async () => {
@@ -88,10 +88,10 @@ describe('Extension: Receive', () => {
};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({temperature: -0.85});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 1, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({temperature: -0.85});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 1, retain: false});
});
it('Should allow to invert cover', async () => {
@@ -107,14 +107,14 @@ describe('Extension: Receive', () => {
linkquality: 10,
});
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/J1_cover', stringify({position: 10, tilt: 20, state: 'OPEN'}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/J1_cover', stringify({position: 10, tilt: 20, state: 'OPEN'}), {
retain: false,
qos: 0,
});
// Inverted
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
settings.set(['devices', device.ieeeAddr, 'invert_cover'], true);
await mockZHEvents.message({
data: {currentPositionLiftPercentage: 90, currentPositionTiltPercentage: 80},
@@ -125,8 +125,8 @@ describe('Extension: Receive', () => {
linkquality: 10,
});
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/J1_cover', stringify({position: 90, tilt: 80, state: 'OPEN'}), {
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/J1_cover', stringify({position: 90, tilt: 80, state: 'OPEN'}), {
retain: false,
qos: 0,
});
@@ -139,8 +139,8 @@ describe('Extension: Receive', () => {
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/button', stringify({action: 'single'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/button', stringify({action: 'single'}), {retain: false, qos: 0});
});
it('Should debounce messages', async () => {
@@ -177,14 +177,14 @@ describe('Extension: Receive', () => {
};
await mockZHEvents.message(payload3);
await flushPromises();
jest.advanceTimersByTime(50);
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(0);
jest.runOnlyPendingTimers();
vi.advanceTimersByTime(50);
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(0);
vi.runOnlyPendingTimers();
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({temperature: 0.08, humidity: 0.01, pressure: 2});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 1, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({temperature: 0.08, humidity: 0.01, pressure: 2});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 1, retain: false});
});
it('Should debounce and retain messages when set via device_options', async () => {
@@ -223,14 +223,14 @@ describe('Extension: Receive', () => {
};
await mockZHEvents.message(payload3);
await flushPromises();
jest.advanceTimersByTime(50);
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(0);
jest.runOnlyPendingTimers();
vi.advanceTimersByTime(50);
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(0);
vi.runOnlyPendingTimers();
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({temperature: 0.08, humidity: 0.01, pressure: 2});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 1, retain: true});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({temperature: 0.08, humidity: 0.01, pressure: 2});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 1, retain: true});
});
it('Should debounce messages only with the same payload values for provided debounce_ignore keys', async () => {
@@ -274,20 +274,19 @@ describe('Extension: Receive', () => {
};
await mockZHEvents.message(humidityMsg);
await flushPromises();
jest.advanceTimersByTime(50);
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({temperature: 0.08, pressure: 2});
jest.runOnlyPendingTimers();
vi.advanceTimersByTime(50);
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({temperature: 0.08, pressure: 2});
vi.runOnlyPendingTimers();
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[1][1])).toStrictEqual({temperature: 0.07, pressure: 2, humidity: 0.03});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[1][1])).toStrictEqual({temperature: 0.07, pressure: 2, humidity: 0.03});
});
it('Should NOT publish old messages from State cache during debouncing', async () => {
// Summary:
// First send multiple measurements to device that is debouncing. Make sure only one message is sent out to mockMQTT. This also ensures first message is cached to "State".
// Then send another measurement to that same device and trigger asynchronous event to push data from Cache. Newest value should be sent out.
const device = devices.WSDCGQ11LM;
settings.set(['devices', device.ieeeAddr, 'debounce'], 0.1);
await mockZHEvents.message({
@@ -315,16 +314,15 @@ describe('Extension: Receive', () => {
linkquality: 10,
});
await flushPromises();
jest.advanceTimersByTime(50);
vi.advanceTimersByTime(50);
// Test that measurements are combined(=debounced)
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(0);
jest.runOnlyPendingTimers();
await flushPromises();
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(0);
vi.runOnlyPendingTimers();
// Test that only one MQTT is sent out and test its values.
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({temperature: 0.08, humidity: 0.01, pressure: 2});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({temperature: 0.08, humidity: 0.01, pressure: 2});
// Send another Zigbee message...
await mockZHEvents.message({
@@ -340,16 +338,15 @@ describe('Extension: Receive', () => {
// Trigger asynchronous event while device is "debouncing" to trigger Message to be sent out from State cache.
await controller.publishEntityState(realDevice, {});
jest.runOnlyPendingTimers();
await flushPromises();
vi.runOnlyPendingTimers();
// Total of 3 messages should have triggered.
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(3);
// Test that message pushed by asynchronous message contains NEW measurement and not old.
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[1][1])).toStrictEqual({temperature: 0.09, humidity: 0.01, pressure: 2});
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[1][1])).toStrictEqual({temperature: 0.09, humidity: 0.01, pressure: 2});
// Test that messages after debouncing contains NEW measurement and not old.
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[2][1])).toStrictEqual({temperature: 0.09, humidity: 0.01, pressure: 2});
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[2][1])).toStrictEqual({temperature: 0.09, humidity: 0.01, pressure: 2});
});
it('Should throttle multiple messages from spamming devices', async () => {
@@ -390,22 +387,22 @@ describe('Extension: Receive', () => {
await mockZHEvents.message(payload3);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/spammer1');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({temperature: 0.01});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: true});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/spammer1');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({temperature: 0.01});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: true});
// Now we try after elapsed time to see if it publishes next message
const timeshift = throttle_for_testing * 2000;
jest.advanceTimersByTime(timeshift);
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
vi.advanceTimersByTime(timeshift);
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
await flushPromises();
expect(mockMQTT.publishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/spammer1');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[1][1])).toStrictEqual({temperature: 0.03});
expect(mockMQTT.publishAsync.mock.calls[1][2]).toStrictEqual({qos: 0, retain: true});
expect(mockMQTTPublishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/spammer1');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[1][1])).toStrictEqual({temperature: 0.03});
expect(mockMQTTPublishAsync.mock.calls[1][2]).toStrictEqual({qos: 0, retain: true});
const data4 = {measuredValue: 4};
const payload4 = {
@@ -419,10 +416,10 @@ describe('Extension: Receive', () => {
await mockZHEvents.message(payload4);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTT.publishAsync.mock.calls[2][0]).toStrictEqual('zigbee2mqtt/spammer1');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[2][1])).toStrictEqual({temperature: 0.04});
expect(mockMQTT.publishAsync.mock.calls[2][2]).toStrictEqual({qos: 0, retain: true});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(3);
expect(mockMQTTPublishAsync.mock.calls[2][0]).toStrictEqual('zigbee2mqtt/spammer1');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[2][1])).toStrictEqual({temperature: 0.04});
expect(mockMQTTPublishAsync.mock.calls[2][2]).toStrictEqual({qos: 0, retain: true});
});
it('Shouldnt republish old state', async () => {
@@ -439,10 +436,10 @@ describe('Extension: Receive', () => {
});
await mockMQTTEvents.message('zigbee2mqtt/bulb/set', stringify({state: 'ON'}));
await flushPromises();
jest.runOnlyPendingTimers();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({state: 'ON'});
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[1][1])).toStrictEqual({state: 'ON'});
vi.runOnlyPendingTimers();
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({state: 'ON'});
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[1][1])).toStrictEqual({state: 'ON'});
});
it('Should handle a zigbee message with 1 precision', async () => {
@@ -459,10 +456,10 @@ describe('Extension: Receive', () => {
};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({temperature: -0.8});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 1, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({temperature: -0.8});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 1, retain: false});
});
it('Should handle a zigbee message with 0 precision', async () => {
@@ -479,10 +476,10 @@ describe('Extension: Receive', () => {
};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({temperature: -1});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 1, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({temperature: -1});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 1, retain: false});
});
it('Should handle a zigbee message with 1 precision when set via device_options', async () => {
@@ -499,10 +496,10 @@ describe('Extension: Receive', () => {
};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({temperature: -0.8});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 1, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({temperature: -0.8});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 1, retain: false});
});
it('Should handle a zigbee message with 2 precision when overrides device_options', async () => {
@@ -520,10 +517,10 @@ describe('Extension: Receive', () => {
};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({temperature: -1});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 1, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({temperature: -1});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 1, retain: false});
});
it('Should handle a zigbee message with voltage 2990', async () => {
@@ -532,10 +529,10 @@ describe('Extension: Receive', () => {
const payload = {data, cluster: 'genBasic', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({battery: 93, voltage: 2990});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({battery: 93, voltage: 2990});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should publish 1 message when converted twice', async () => {
@@ -544,16 +541,16 @@ describe('Extension: Receive', () => {
const payload = {data, cluster: 'genBasic', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/occupancy_sensor');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/occupancy_sensor');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({
battery: 100,
illuminance: 381,
voltage: 3045,
device_temperature: 19,
power_outage_count: 34,
});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should publish 1 message when converted twice', async () => {
@@ -562,7 +559,7 @@ describe('Extension: Receive', () => {
const payload = {data, cluster: 'genBasic', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(0);
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(0);
});
it('Should publish last_seen epoch', async () => {
@@ -572,10 +569,10 @@ describe('Extension: Receive', () => {
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(typeof JSON.parse(mockMQTT.publishAsync.mock.calls[0][1]).last_seen).toBe('number');
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(typeof JSON.parse(mockMQTTPublishAsync.mock.calls[0][1]).last_seen).toBe('number');
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should publish last_seen ISO_8601', async () => {
@@ -585,10 +582,10 @@ describe('Extension: Receive', () => {
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(typeof JSON.parse(mockMQTT.publishAsync.mock.calls[0][1]).last_seen).toBe('string');
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(typeof JSON.parse(mockMQTTPublishAsync.mock.calls[0][1]).last_seen).toBe('string');
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should publish last_seen ISO_8601_local', async () => {
@@ -598,10 +595,10 @@ describe('Extension: Receive', () => {
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(typeof JSON.parse(mockMQTT.publishAsync.mock.calls[0][1]).last_seen).toBe('string');
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button_double_key');
expect(typeof JSON.parse(mockMQTTPublishAsync.mock.calls[0][1]).last_seen).toBe('string');
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should handle messages from Xiaomi router devices', async () => {
@@ -610,9 +607,9 @@ describe('Extension: Receive', () => {
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 20};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/power_plug', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTT.publishAsync).toHaveBeenCalledWith('zigbee2mqtt/switch_group', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/power_plug', stringify({state: 'ON'}), {retain: false, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith('zigbee2mqtt/switch_group', stringify({state: 'ON'}), {retain: false, qos: 0});
});
it('Should not handle messages from coordinator', async () => {
@@ -621,7 +618,7 @@ describe('Extension: Receive', () => {
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(0);
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(0);
});
it('Should not handle messages from still interviewing devices with unknown definition', async () => {
@@ -631,7 +628,7 @@ describe('Extension: Receive', () => {
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(0);
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(0);
expect(mockLogger.debug).toHaveBeenCalledWith(`Skipping message, still interviewing`);
});
@@ -649,29 +646,29 @@ describe('Extension: Receive', () => {
};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/ikea_onoff');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({action: 'brightness_stop'});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/ikea_onoff');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({action: 'brightness_stop'});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should add elapsed', async () => {
settings.set(['advanced', 'elapsed'], true);
const device = devices.E1743;
const payload = {data: {}, cluster: 'genLevelCtrl', device, endpoint: device.getEndpoint(1), type: 'commandStopWithOnOff'};
jest.spyOn(Date, 'now').mockReturnValueOnce(150).mockReturnValueOnce(200);
vi.spyOn(Date, 'now').mockReturnValueOnce(150).mockReturnValueOnce(200);
await mockZHEvents.message({...payload, meta: {zclTransactionSequenceNumber: 2}});
await flushPromises();
await mockZHEvents.message({...payload, meta: {zclTransactionSequenceNumber: 3}});
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/ikea_onoff');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({action: 'brightness_stop'});
expect(mockMQTT.publishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTT.publishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/ikea_onoff');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[1][1])).toMatchObject({action: 'brightness_stop'});
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[1][1]).elapsed).toBe(50);
expect(mockMQTT.publishAsync.mock.calls[1][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(2);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/ikea_onoff');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({action: 'brightness_stop'});
expect(mockMQTTPublishAsync.mock.calls[0][2]).toStrictEqual({qos: 0, retain: false});
expect(mockMQTTPublishAsync.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/ikea_onoff');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[1][1])).toMatchObject({action: 'brightness_stop'});
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[1][1]).elapsed).toBe(50);
expect(mockMQTTPublishAsync.mock.calls[1][2]).toStrictEqual({qos: 0, retain: false});
});
it('Should log when message is from supported device but has no converters', async () => {
@@ -680,7 +677,7 @@ describe('Extension: Receive', () => {
const payload = {data, cluster: 'genBinaryOutput', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 20};
await mockZHEvents.message(payload);
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(0);
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(0);
expect(mockLogger.debug).toHaveBeenCalledWith(
"No converter available for 'ZNCZ02LM' with cluster 'genBinaryOutput' and type 'attributeReport' and data '{\"inactiveText\":\"hello\"}'",
);
@@ -702,11 +699,11 @@ describe('Extension: Receive', () => {
meta: {zclTransactionSequenceNumber: 1},
});
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/SP600_NEW');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({energy: 0.66, power: 49.6});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/SP600_NEW');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({energy: 0.66, power: 49.6});
mockMQTT.publishAsync.mockClear();
mockMQTTPublishAsync.mockClear();
const SP600_OLD = devices.SP600_OLD;
await mockZHEvents.message({
data,
@@ -718,9 +715,9 @@ describe('Extension: Receive', () => {
meta: {zclTransactionSequenceNumber: 2},
});
await flushPromises();
expect(mockMQTT.publishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/SP600_OLD');
expect(JSON.parse(mockMQTT.publishAsync.mock.calls[0][1])).toStrictEqual({energy: 6.65, power: 496});
expect(mockMQTTPublishAsync).toHaveBeenCalledTimes(1);
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/SP600_OLD');
expect(JSON.parse(mockMQTTPublishAsync.mock.calls[0][1])).toStrictEqual({energy: 6.65, power: 496});
});
it('Should emit DevicesChanged event when a converter announces changed exposes', async () => {
@@ -728,6 +725,6 @@ describe('Extension: Receive', () => {
const data = {deviceMode: 0};
const payload = {data, cluster: 'boschSpecific', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await mockZHEvents.message(payload);
expect(mockMQTT.publishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/devices');
expect(mockMQTTPublishAsync.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/bridge/devices');
});
});