Refactor HomeAssistant extension to TypeScript

This commit is contained in:
Koen Kanters committed 2021-09-22 18:04:57 +02:00
1 parent 74ee5581fe
commit 89dea86b3a
9 files changed
+274 -325

No files matched your search

+46 -133
View File
@@ -8,25 +8,38 @@ const MQTT = require('./stub/mqtt');
const Controller = require('../lib/controller');
const fs = require('fs');
const path = require('path');
const HomeAssistant = require('../lib/extension/homeassistant');
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
describe('HomeAssistant extension', () => {
let version;
let controller;
let extension;
let resetExtension = async () => {
await controller.enableDisableExtension(false, 'HomeAssistant');
MQTT.publish.mockClear();
await controller.enableDisableExtension(true, 'HomeAssistant');
extension = controller.extensions.find((e) => e.constructor.name === 'HomeAssistant');
}
beforeEach(async () => {
data.writeDefaultConfiguration();
settings.reRead();
data.writeEmptyState();
controller.state._load();
await resetExtension();
});
beforeAll(async () => {
version = await require('../lib/util/utils').getZigbee2MQTTVersion();
version = `Zigbee2MQTT ${version.version}`;
jest.useFakeTimers();
settings.set(['homeassistant'], true);
data.writeDefaultConfiguration();
settings.reRead();
data.writeEmptyState();
MQTT.publish.mockClear();
settings.set(['homeassistant'], true);
});
beforeAll(async () => {
jest.useFakeTimers();
controller = new Controller(false);
await controller.start();
});
afterAll(async () => {
@@ -35,10 +48,8 @@ describe('HomeAssistant extension', () => {
it('Should not have duplicate type/object_ids in a mapping', () => {
const duplicated = [];
const ha = new HomeAssistant(null, null, null, null, {on: () => {}});
require('zigbee-herdsman-converters').devices.forEach((d) => {
const mapping = ha._getMapping()[d.model];
const mapping = extension._getMapping()[d.model];
const cfg_type_object_ids = [];
mapping.forEach((c) => {
@@ -54,9 +65,6 @@ describe('HomeAssistant extension', () => {
});
it('Should discover devices and groups', async () => {
const controller = new Controller(false);
await controller.start();
let payload;
await flushPromises();
@@ -357,8 +365,7 @@ describe('HomeAssistant extension', () => {
retain: false,
})
const controller = new Controller(false);
await controller.start();
await resetExtension();
let payload;
await flushPromises();
@@ -469,8 +476,7 @@ describe('HomeAssistant extension', () => {
retain: false,
})
const controller = new Controller(false);
await controller.start();
await resetExtension();
let payload;
await flushPromises();
@@ -549,8 +555,7 @@ describe('HomeAssistant extension', () => {
},
})
const controller = new Controller(false);
await controller.start();
await resetExtension();
let payload;
await flushPromises();
@@ -592,9 +597,7 @@ describe('HomeAssistant extension', () => {
retain: false,
})
const controller = new Controller(false);
await controller.start();
await resetExtension();
await flushPromises();
const topics = MQTT.publish.mock.calls.map((c) => c[0]);
@@ -610,10 +613,7 @@ describe('HomeAssistant extension', () => {
retain: false,
})
const controller = new Controller(false);
await controller.start();
await flushPromises();
await resetExtension();
const topics = MQTT.publish.mock.calls.map((c) => c[0]);
expect(topics).not.toContain('homeassistant/sensor/0x0017880104e45522/humidity/config')
@@ -621,11 +621,7 @@ describe('HomeAssistant extension', () => {
});
it('Should discover devices with fan', async () => {
const controller = new Controller(false);
await controller.start();
let payload;
await flushPromises();
payload = {
"state_topic":"zigbee2mqtt/fan",
@@ -667,11 +663,7 @@ describe('HomeAssistant extension', () => {
});
it('Should discover thermostat devices', async () => {
const controller = new Controller(false);
await controller.start();
let payload;
await flushPromises();
payload = {
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
@@ -727,11 +719,7 @@ describe('HomeAssistant extension', () => {
});
it('Should discover devices with cover_position', async () => {
const controller = new Controller(false);
await controller.start();
let payload;
await flushPromises();
payload = {
command_topic: 'zigbee2mqtt/smart vent/set',
@@ -763,11 +751,9 @@ describe('HomeAssistant extension', () => {
it('Should discover devices with custom homeassistant_discovery_topic', async () => {
settings.set(['advanced', 'homeassistant_discovery_topic'], 'my_custom_discovery_topic')
const controller = new Controller(false);
await controller.start();
await resetExtension();
let payload;
await flushPromises();
payload = {
'unit_of_measurement': '°C',
@@ -799,6 +785,7 @@ describe('HomeAssistant extension', () => {
it('Should throw error when starting with attributes output', async () => {
settings.set(['experimental', 'output'], 'attribute')
settings.set(['homeassistant'], true)
expect(() => {
const controller = new Controller(false);
}).toThrowError('Home Assistant integration is not possible with attribute output!');
@@ -807,15 +794,12 @@ describe('HomeAssistant extension', () => {
it('Should warn when starting with cache_state false', async () => {
settings.set(['advanced', 'cache_state'], false);
logger.warn.mockClear();
const controller = new Controller(false);
await resetExtension();
expect(logger.warn).toHaveBeenCalledWith("In order for Home Assistant integration to work properly set `cache_state: true");
});
it('Should set missing values to null', async () => {
// https://github.com/Koenkk/zigbee2mqtt/issues/6987
const controller = new Controller(false);
await controller.start();
await flushPromises();
const device = zigbeeHerdsman.devices.WSDCGQ11LM;
const data = {measuredValue: -85}
const payload = {data, cluster: 'msTemperatureMeasurement', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
@@ -832,9 +816,6 @@ describe('HomeAssistant extension', () => {
});
it('Should copy hue/saturtion to h/s if present', async () => {
const controller = new Controller(false);
await controller.start();
await flushPromises();
const device = zigbeeHerdsman.devices.bulb_color;
const data = {currentHue: 0, currentSaturation: 254}
const payload = {data, cluster: 'lightingColorCtrl', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
@@ -851,9 +832,6 @@ describe('HomeAssistant extension', () => {
});
it('Should not copy hue/saturtion if properties are missing', async () => {
const controller = new Controller(false);
await controller.start();
await flushPromises();
const device = zigbeeHerdsman.devices.bulb_color;
const data = {currentX: 29991, currentY: 26872};
const payload = {data, cluster: 'lightingColorCtrl', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
@@ -870,9 +848,6 @@ describe('HomeAssistant extension', () => {
});
it('Should not copy hue/saturtion if color is missing', async () => {
const controller = new Controller(false);
await controller.start();
await flushPromises();
const device = zigbeeHerdsman.devices.bulb_color;
const data = {onOff: 1}
const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
@@ -889,9 +864,6 @@ describe('HomeAssistant extension', () => {
});
it('Shouldt discover when already discovered', async () => {
const controller = new Controller(false);
await controller.start();
await flushPromises();
const device = zigbeeHerdsman.devices.WSDCGQ11LM;
const data = {measuredValue: -85}
const payload = {data, cluster: 'msTemperatureMeasurement', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
@@ -903,9 +875,6 @@ describe('HomeAssistant extension', () => {
});
it('Should discover when not discovered yet', async () => {
const controller = new Controller(false);
await controller.start();
await flushPromises();
controller.extensions.find((e) => e.constructor.name === 'HomeAssistant').discovered = {};
const device = zigbeeHerdsman.devices.WSDCGQ11LM;
const data = {measuredValue: -85}
@@ -942,9 +911,6 @@ describe('HomeAssistant extension', () => {
});
it('Shouldnt discover when device leaves', async () => {
const controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
controller.extensions.find((e) => e.constructor.name === 'HomeAssistant').discovered = {};
const device = zigbeeHerdsman.devices.bulb;
const payload = {ieeeAddr: device.ieeeAddr};
@@ -955,8 +921,8 @@ describe('HomeAssistant extension', () => {
it('Should send all status when home assistant comes online (default topic)', async () => {
data.writeDefaultState();
const controller = new Controller(jest.fn(), jest.fn());
await controller.start();
extension.state._load();
await resetExtension();
expect(MQTT.subscribe).toHaveBeenCalledWith('homeassistant/status');
await flushPromises();
MQTT.publish.mockClear();
@@ -980,9 +946,8 @@ describe('HomeAssistant extension', () => {
it('Should send all status when home assistant comes online', async () => {
data.writeDefaultState();
const controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
extension.state._load();
await resetExtension();
expect(MQTT.subscribe).toHaveBeenCalledWith('hass/status');
MQTT.publish.mockClear();
await MQTT.events.message('hass/status', 'online');
@@ -1005,8 +970,8 @@ describe('HomeAssistant extension', () => {
it('Shouldnt send all status when home assistant comes offline', async () => {
data.writeDefaultState();
const controller = new Controller(jest.fn(), jest.fn());
await controller.start();
extension.state._load();
await resetExtension();
await flushPromises();
MQTT.publish.mockClear();
await MQTT.events.message('hass/status', 'offline');
@@ -1018,9 +983,8 @@ describe('HomeAssistant extension', () => {
it('Shouldnt send all status when home assistant comes online with different topic', async () => {
data.writeDefaultState();
const controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
extension.state._load();
await resetExtension();
MQTT.publish.mockClear();
await MQTT.events.message('hass/status_different', 'offline');
await flushPromises();
@@ -1031,11 +995,9 @@ describe('HomeAssistant extension', () => {
it('Should discover devices with availability', async () => {
settings.set(['advanced', 'availability_timeout'], 1)
const controller = new Controller(false);
await controller.start();
await resetExtension();
let payload;
await flushPromises();
payload = {
'unit_of_measurement': '°C',
@@ -1066,9 +1028,6 @@ describe('HomeAssistant extension', () => {
});
it('Should clear discovery when device is removed', async () => {
const controller = new Controller(false);
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/config/remove', 'weather_sensor');
await flushPromises();
@@ -1106,9 +1065,6 @@ describe('HomeAssistant extension', () => {
});
it('Should not clear discovery when unsupported device is removed', async () => {
const controller = new Controller(false);
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/config/remove', 'unsupported2');
await flushPromises();
@@ -1116,9 +1072,6 @@ describe('HomeAssistant extension', () => {
});
it('Should refresh discovery when device is renamed', async () => {
const controller = new Controller(false);
await controller.start();
await flushPromises();
await MQTT.events.message('homeassistant/device_automation/0x0017880104e45522/action_double/config', stringify({topic: 'zigbee2mqtt/weather_sensor/action'}));
await flushPromises();
MQTT.publish.mockClear();
@@ -1183,9 +1136,6 @@ describe('HomeAssistant extension', () => {
});
it('Shouldnt refresh discovery when device is renamed and homeassistant_rename is false', async () => {
const controller = new Controller(false);
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', stringify({"from": "weather_sensor", "to": "weather_sensor_renamed","homeassistant_rename":false}));
await flushPromises();
@@ -1226,9 +1176,6 @@ describe('HomeAssistant extension', () => {
});
it('Should discover update_available sensor when device supports it', async () => {
const controller = new Controller(false);
await controller.start();
await flushPromises();
const payload = {
"payload_on":true,
"payload_off":false,
@@ -1259,10 +1206,6 @@ describe('HomeAssistant extension', () => {
});
it('Should discover trigger when click is published', async () => {
const controller = new Controller(false);
await controller.start();
await flushPromises();
const discovered = MQTT.publish.mock.calls.filter((c) => c[0].includes('0x0017880104e45520')).map((c) => c[0]);
expect(discovered.length).toBe(5);
expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/click/config');
@@ -1415,9 +1358,7 @@ describe('HomeAssistant extension', () => {
settings.set(['device_options'], {
homeassistant: {device_automation: null},
})
const controller = new Controller(false);
await controller.start();
await flushPromises();
await resetExtension();
MQTT.publish.mockClear();
const device = zigbeeHerdsman.devices.WXKG11LM;
@@ -1439,9 +1380,7 @@ describe('HomeAssistant extension', () => {
friendly_name: 'weather_sensor',
retain: false,
})
const controller = new Controller(false);
await controller.start();
await flushPromises();
await resetExtension();
const discovered = MQTT.publish.mock.calls.filter((c) => c[0].includes('0x0017880104e45520')).map((c) => c[0]);
expect(discovered.length).toBe(4);
@@ -1452,9 +1391,7 @@ describe('HomeAssistant extension', () => {
it('Should disable Home Assistant legacy triggers', async () => {
settings.set(['advanced', 'homeassistant_legacy_triggers'], false);
const controller = new Controller(false);
await controller.start();
await flushPromises();
await resetExtension();
const discovered = MQTT.publish.mock.calls.filter((c) => c[0].includes('0x0017880104e45520')).map((c) => c[0]);
expect(discovered.length).toBe(3);
@@ -1511,9 +1448,6 @@ describe('HomeAssistant extension', () => {
});
it('Should republish payload to postfix topic with lightWithPostfix config', async () => {
const controller = new Controller(false);
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/U202DST600ZB/l2/set', stringify({state: 'ON', brightness: 20}));
@@ -1524,9 +1458,6 @@ describe('HomeAssistant extension', () => {
});
it('Shouldnt crash in onPublishEntityState on group publish', async () => {
const controller = new Controller(false);
await controller.start();
await flushPromises();
logger.error.mockClear();
MQTT.publish.mockClear();
@@ -1536,9 +1467,6 @@ describe('HomeAssistant extension', () => {
});
it('Should counter an action payload with an empty payload', async () => {
const controller = new Controller(false);
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
const device = zigbeeHerdsman.devices.WXKG11LM;
settings.set(['devices', device.ieeeAddr, 'legacy'], false);
@@ -1560,10 +1488,7 @@ describe('HomeAssistant extension', () => {
it('Load Home Assistant mapping from external converters', async () => {
fs.copyFileSync(path.join(__dirname, 'assets', 'mock-external-converter-multiple.js'), path.join(data.mockDir, 'mock-external-converter-multiple.js'));
settings.set(['external_converters'], ['mock-external-converter-multiple.js']);
const controller = new Controller(jest.fn(), jest.fn());
const ha = controller.extensions.find((e) => e.constructor.name === 'HomeAssistant');
await controller.start();
await flushPromises();
await resetExtension();
const homeassistantSwitch = {
type: 'switch',
@@ -1575,14 +1500,10 @@ describe('HomeAssistant extension', () => {
command_topic: true,
},
};
expect(ha._getMapping()['external_converters_device_1']).toEqual([homeassistantSwitch]);
expect(extension._getMapping()['external_converters_device_1']).toEqual([homeassistantSwitch]);
});
it('Should clear outdated configs', async () => {
let controller = new Controller(jest.fn(), jest.fn());
await controller.start();
await flushPromises();
// Non-existing group -> clear
MQTT.publish.mockClear();
await MQTT.events.message('homeassistant/light/1221051039810110150109113116116_91231/light/config', stringify({availability: [{topic: 'zigbee2mqtt/bridge/state'}]}));
@@ -1655,12 +1576,8 @@ describe('HomeAssistant extension', () => {
expect(MQTT.publish).toHaveBeenCalledTimes(0);
// Device was flagged to be excluded from homeassistant discovery
await controller.stop();
await flushPromises();
settings.set(['devices', '0x000b57fffec6a5b2', 'homeassistant'], null);
controller = new Controller(false);
await controller.start();
await flushPromises();
await resetExtension();
MQTT.publish.mockClear();
await MQTT.events.message('homeassistant/sensor/0x000b57fffec6a5b2/update_available/config', stringify({availability: [{topic: 'zigbee2mqtt/bridge/state'}]}));
@@ -1674,8 +1591,7 @@ describe('HomeAssistant extension', () => {
it('Should not have Home Assistant legacy entity attributes when disabled', async () => {
settings.set(['advanced', 'homeassistant_legacy_entity_attributes'], false);
const controller = new Controller(false);
await controller.start();
await resetExtension();
let payload;
await flushPromises();
@@ -1708,9 +1624,6 @@ describe('HomeAssistant extension', () => {
});
it('Should rediscover group when device is added to it', async () => {
const controller = new Controller(false);
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
MQTT.events.message('zigbee2mqtt/bridge/request/group/members/add', stringify({group: 'ha_discovery_group', device: 'wall_switch_double/left'}));
await flushPromises();