2022-05-20 23:37:39 -07:00
|
|
|
const path = require('path');
|
|
|
|
|
2020-02-08 11:55:27 -07:00
|
|
|
const data = require('./stub/data');
|
|
|
|
const logger = require('./stub/logger');
|
2024-01-03 02:51:05 -07:00
|
|
|
const sleep = require('./stub/sleep');
|
2020-02-08 11:55:27 -07:00
|
|
|
const zigbeeHerdsman = require('./stub/zigbeeHerdsman');
|
|
|
|
const MQTT = require('./stub/mqtt');
|
|
|
|
const settings = require('../lib/util/settings');
|
|
|
|
const Controller = require('../lib/controller');
|
2021-07-05 11:46:53 -07:00
|
|
|
const flushPromises = require('./lib/flushPromises');
|
2020-02-08 11:55:27 -07:00
|
|
|
const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters');
|
2020-09-24 09:06:43 -07:00
|
|
|
const stringify = require('json-stable-stringify-without-jsonify');
|
2022-01-04 12:29:17 -07:00
|
|
|
const zigbeeOTA = require('zigbee-herdsman-converters/lib/ota/zigbeeOTA');
|
|
|
|
|
|
|
|
const spyUseIndexOverride = jest.spyOn(zigbeeOTA, 'useIndexOverride');
|
2020-02-08 11:55:27 -07:00
|
|
|
|
|
|
|
describe('OTA update', () => {
|
|
|
|
let controller;
|
|
|
|
|
2022-01-04 12:29:17 -07:00
|
|
|
let resetExtension = async () => {
|
|
|
|
await controller.enableDisableExtension(false, 'OTAUpdate');
|
|
|
|
await controller.enableDisableExtension(true, 'OTAUpdate');
|
2024-06-24 11:58:47 -07:00
|
|
|
};
|
2022-01-04 12:29:17 -07:00
|
|
|
|
2021-07-21 10:35:14 -07:00
|
|
|
const mockClear = (mapped) => {
|
2020-02-08 11:55:27 -07:00
|
|
|
mapped.ota.updateToLatest = jest.fn();
|
|
|
|
mapped.ota.isUpdateAvailable = jest.fn();
|
2024-06-24 11:58:47 -07:00
|
|
|
};
|
2021-07-21 10:35:14 -07:00
|
|
|
|
2021-07-05 11:46:53 -07:00
|
|
|
beforeAll(async () => {
|
2020-02-08 11:55:27 -07:00
|
|
|
data.writeDefaultConfiguration();
|
2021-03-09 11:50:05 -07:00
|
|
|
settings.reRead();
|
2021-07-05 11:46:53 -07:00
|
|
|
data.writeDefaultConfiguration();
|
2022-01-09 14:28:44 -07:00
|
|
|
settings.set(['ota', 'ikea_ota_use_test_url'], true);
|
2021-07-05 11:46:53 -07:00
|
|
|
settings.reRead();
|
|
|
|
jest.useFakeTimers();
|
2021-02-06 08:32:20 -07:00
|
|
|
controller = new Controller(jest.fn(), jest.fn());
|
2024-01-03 02:51:05 -07:00
|
|
|
sleep.mock();
|
2020-02-08 11:55:27 -07:00
|
|
|
await controller.start();
|
2022-12-12 13:35:10 -07:00
|
|
|
await jest.runOnlyPendingTimers();
|
2020-02-08 11:55:27 -07:00
|
|
|
await flushPromises();
|
2021-07-05 11:46:53 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(async () => {
|
|
|
|
settings.set(['ota', 'disable_automatic_update_check'], false);
|
|
|
|
jest.runOnlyPendingTimers();
|
|
|
|
});
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
jest.useRealTimers();
|
2024-01-03 02:51:05 -07:00
|
|
|
sleep.restore();
|
2021-07-05 11:46:53 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
const extension = controller.extensions.find((e) => e.constructor.name === 'OTAUpdate');
|
|
|
|
extension.lastChecked = {};
|
|
|
|
extension.inProgress = new Set();
|
|
|
|
controller.state.state = {};
|
2020-02-08 11:55:27 -07:00
|
|
|
MQTT.publish.mockClear();
|
|
|
|
});
|
|
|
|
|
2024-01-30 12:12:52 -07:00
|
|
|
it('Should OTA update a device', async () => {
|
2020-02-08 11:55:27 -07:00
|
|
|
const device = zigbeeHerdsman.devices.bulb;
|
2020-02-09 12:44:37 -07:00
|
|
|
const endpoint = device.endpoints[0];
|
|
|
|
let count = 0;
|
|
|
|
endpoint.read.mockImplementation(() => {
|
|
|
|
count++;
|
2024-06-24 11:58:47 -07:00
|
|
|
return {swBuildId: count, dateCode: '2019010' + count};
|
2020-02-09 12:44:37 -07:00
|
|
|
});
|
2024-06-24 11:58:47 -07:00
|
|
|
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
2020-02-08 11:55:27 -07:00
|
|
|
mockClear(mapped);
|
|
|
|
logger.info.mockClear();
|
2020-02-09 12:44:37 -07:00
|
|
|
device.save.mockClear();
|
2024-04-04 12:33:39 -07:00
|
|
|
mapped.ota.updateToLatest.mockImplementationOnce((a, onUpdate) => {
|
2020-02-09 12:44:37 -07:00
|
|
|
onUpdate(0, null);
|
2020-08-02 14:09:43 -07:00
|
|
|
onUpdate(10, 3600.2123);
|
2022-12-18 14:05:16 -07:00
|
|
|
return 90;
|
2020-02-08 11:55:27 -07:00
|
|
|
});
|
|
|
|
|
2020-07-13 14:00:33 -07:00
|
|
|
MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/update', 'bulb');
|
2020-02-08 11:55:27 -07:00
|
|
|
await flushPromises();
|
2020-02-09 12:44:37 -07:00
|
|
|
expect(logger.info).toHaveBeenCalledWith(`Updating 'bulb' to latest firmware`);
|
|
|
|
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(0);
|
2020-02-08 11:55:27 -07:00
|
|
|
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(1);
|
2024-04-04 12:33:39 -07:00
|
|
|
expect(mapped.ota.updateToLatest).toHaveBeenCalledWith(device, expect.any(Function));
|
2020-04-24 13:38:51 -07:00
|
|
|
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 0.00%`);
|
2021-02-21 12:47:00 -07:00
|
|
|
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 10.00%, ≈ 60 minutes remaining`);
|
2022-01-16 10:25:53 -07:00
|
|
|
expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb'`);
|
2024-06-24 11:58:47 -07:00
|
|
|
expect(logger.info).toHaveBeenCalledWith(
|
|
|
|
`Device 'bulb' was updated from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190102","softwareBuildID":2}'`,
|
|
|
|
);
|
2021-12-19 10:12:16 -07:00
|
|
|
expect(device.save).toHaveBeenCalledTimes(2);
|
2024-06-24 11:58:47 -07:00
|
|
|
expect(endpoint.read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {sendPolicy: 'immediate'});
|
|
|
|
expect(endpoint.read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {sendPolicy: undefined});
|
2020-08-02 14:09:43 -07:00
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
|
|
|
'zigbee2mqtt/bulb',
|
2024-06-24 11:58:47 -07:00
|
|
|
stringify({update_available: false, update: {state: 'updating', progress: 0}}),
|
|
|
|
{retain: true, qos: 0},
|
|
|
|
expect.any(Function),
|
2020-08-02 14:09:43 -07:00
|
|
|
);
|
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
|
|
|
'zigbee2mqtt/bulb',
|
2024-06-24 11:58:47 -07:00
|
|
|
stringify({update_available: false, update: {state: 'updating', progress: 10, remaining: 3600}}),
|
|
|
|
{retain: true, qos: 0},
|
|
|
|
expect.any(Function),
|
2020-08-02 14:09:43 -07:00
|
|
|
);
|
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
|
|
|
'zigbee2mqtt/bulb',
|
2024-06-24 11:58:47 -07:00
|
|
|
stringify({update_available: false, update: {state: 'idle', installed_version: 90, latest_version: 90}}),
|
|
|
|
{retain: true, qos: 0},
|
|
|
|
expect.any(Function),
|
2020-08-02 14:09:43 -07:00
|
|
|
);
|
2020-07-08 14:23:44 -07:00
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
2020-07-13 14:00:33 -07:00
|
|
|
'zigbee2mqtt/bridge/response/device/ota_update/update',
|
2024-06-24 11:58:47 -07:00
|
|
|
stringify({
|
|
|
|
data: {from: {date_code: '20190101', software_build_id: 1}, id: 'bulb', to: {date_code: '20190102', software_build_id: 2}},
|
|
|
|
status: 'ok',
|
|
|
|
}),
|
|
|
|
{retain: false, qos: 0},
|
|
|
|
expect.any(Function),
|
2020-07-08 14:23:44 -07:00
|
|
|
);
|
2024-06-24 11:58:47 -07:00
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), {retain: true, qos: 0}, expect.any(Function));
|
2020-02-08 11:55:27 -07:00
|
|
|
});
|
|
|
|
|
2020-02-09 12:44:37 -07:00
|
|
|
it('Should handle when OTA update fails', async () => {
|
2020-02-08 11:55:27 -07:00
|
|
|
const device = zigbeeHerdsman.devices.bulb;
|
2020-02-09 12:44:37 -07:00
|
|
|
const endpoint = device.endpoints[0];
|
2024-06-24 11:58:47 -07:00
|
|
|
endpoint.read.mockImplementation(() => {
|
|
|
|
return {swBuildId: 1, dateCode: '2019010'};
|
|
|
|
});
|
|
|
|
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
2020-02-08 11:55:27 -07:00
|
|
|
mockClear(mapped);
|
2020-02-09 12:44:37 -07:00
|
|
|
device.save.mockClear();
|
2024-04-04 12:33:39 -07:00
|
|
|
mapped.ota.updateToLatest.mockImplementationOnce((a, onUpdate) => {
|
2020-02-09 12:44:37 -07:00
|
|
|
throw new Error('Update failed');
|
|
|
|
});
|
2020-02-08 11:55:27 -07:00
|
|
|
|
2024-06-24 11:58:47 -07:00
|
|
|
MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/update', stringify({id: 'bulb'}));
|
2020-02-08 11:55:27 -07:00
|
|
|
await flushPromises();
|
2020-08-02 14:09:43 -07:00
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
|
|
|
'zigbee2mqtt/bulb',
|
2024-06-24 11:58:47 -07:00
|
|
|
stringify({update_available: true, update: {state: 'available'}}),
|
|
|
|
{retain: true, qos: 0},
|
|
|
|
expect.any(Function),
|
2020-08-02 14:09:43 -07:00
|
|
|
);
|
2020-07-08 14:23:44 -07:00
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
2020-07-13 14:00:33 -07:00
|
|
|
'zigbee2mqtt/bridge/response/device/ota_update/update',
|
2024-06-24 11:58:47 -07:00
|
|
|
stringify({data: {id: 'bulb'}, status: 'error', error: "Update of 'bulb' failed (Update failed)"}),
|
|
|
|
{retain: false, qos: 0},
|
|
|
|
expect.any(Function),
|
2020-07-08 14:23:44 -07:00
|
|
|
);
|
2020-02-08 11:55:27 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
it('Should be able to check if OTA update is available', async () => {
|
|
|
|
const device = zigbeeHerdsman.devices.bulb;
|
2024-06-24 11:58:47 -07:00
|
|
|
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
2020-02-08 11:55:27 -07:00
|
|
|
mockClear(mapped);
|
2020-02-09 12:44:37 -07:00
|
|
|
|
2022-12-18 14:05:16 -07:00
|
|
|
mapped.ota.isUpdateAvailable.mockReturnValueOnce({available: false, currentFileVersion: 10, otaFileVersion: 10});
|
2024-06-24 11:58:47 -07:00
|
|
|
MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/check', 'bulb');
|
2020-02-08 11:55:27 -07:00
|
|
|
await flushPromises();
|
|
|
|
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1);
|
|
|
|
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0);
|
2020-07-08 14:23:44 -07:00
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
2020-07-13 14:00:33 -07:00
|
|
|
'zigbee2mqtt/bridge/response/device/ota_update/check',
|
2024-06-24 11:58:47 -07:00
|
|
|
stringify({data: {id: 'bulb', updateAvailable: false}, status: 'ok'}),
|
|
|
|
{retain: false, qos: 0},
|
|
|
|
expect.any(Function),
|
2020-07-08 14:23:44 -07:00
|
|
|
);
|
2020-02-09 12:44:37 -07:00
|
|
|
|
2020-07-08 14:23:44 -07:00
|
|
|
MQTT.publish.mockClear();
|
2022-12-18 14:05:16 -07:00
|
|
|
mapped.ota.isUpdateAvailable.mockReturnValueOnce({available: true, currentFileVersion: 10, otaFileVersion: 12});
|
2024-06-24 11:58:47 -07:00
|
|
|
MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/check', 'bulb');
|
2020-02-09 12:44:37 -07:00
|
|
|
await flushPromises();
|
|
|
|
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(2);
|
|
|
|
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0);
|
2020-07-08 14:23:44 -07:00
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
2020-07-13 14:00:33 -07:00
|
|
|
'zigbee2mqtt/bridge/response/device/ota_update/check',
|
2024-06-24 11:58:47 -07:00
|
|
|
stringify({data: {id: 'bulb', updateAvailable: true}, status: 'ok'}),
|
|
|
|
{retain: false, qos: 0},
|
|
|
|
expect.any(Function),
|
2020-07-08 14:23:44 -07:00
|
|
|
);
|
2020-02-09 12:44:37 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
it('Should handle if OTA update check fails', async () => {
|
|
|
|
const device = zigbeeHerdsman.devices.bulb;
|
2024-06-24 11:58:47 -07:00
|
|
|
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
2020-02-09 12:44:37 -07:00
|
|
|
mockClear(mapped);
|
2024-06-24 11:58:47 -07:00
|
|
|
mapped.ota.isUpdateAvailable.mockImplementationOnce(() => {
|
|
|
|
throw new Error('RF signals disturbed because of dogs barking');
|
|
|
|
});
|
2020-02-09 12:44:37 -07:00
|
|
|
|
2024-06-24 11:58:47 -07:00
|
|
|
MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/check', 'bulb');
|
2020-02-09 12:44:37 -07:00
|
|
|
await flushPromises();
|
|
|
|
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1);
|
|
|
|
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0);
|
2020-07-08 14:23:44 -07:00
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
2020-07-13 14:00:33 -07:00
|
|
|
'zigbee2mqtt/bridge/response/device/ota_update/check',
|
2024-06-24 11:58:47 -07:00
|
|
|
stringify({
|
|
|
|
data: {id: 'bulb'},
|
|
|
|
status: 'error',
|
|
|
|
error: `Failed to check if update available for 'bulb' (RF signals disturbed because of dogs barking)`,
|
|
|
|
}),
|
|
|
|
{retain: false, qos: 0},
|
|
|
|
expect.any(Function),
|
2020-07-08 14:23:44 -07:00
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Should fail when device does not exist', async () => {
|
2024-06-24 11:58:47 -07:00
|
|
|
MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/check', 'not_existing_deviceooo');
|
2020-07-08 14:23:44 -07:00
|
|
|
await flushPromises();
|
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
2020-07-13 14:00:33 -07:00
|
|
|
'zigbee2mqtt/bridge/response/device/ota_update/check',
|
2024-06-24 11:58:47 -07:00
|
|
|
stringify({data: {id: 'not_existing_deviceooo'}, status: 'error', error: `Device 'not_existing_deviceooo' does not exist`}),
|
|
|
|
{retain: false, qos: 0},
|
|
|
|
expect.any(Function),
|
2020-07-08 14:23:44 -07:00
|
|
|
);
|
2020-02-08 11:55:27 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
it('Should not check for OTA when device does not support it', async () => {
|
2024-06-24 11:58:47 -07:00
|
|
|
MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/check', 'dimmer_wall_switch');
|
2020-02-08 11:55:27 -07:00
|
|
|
await flushPromises();
|
2020-07-08 14:23:44 -07:00
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
2020-07-13 14:00:33 -07:00
|
|
|
'zigbee2mqtt/bridge/response/device/ota_update/check',
|
2024-06-24 11:58:47 -07:00
|
|
|
stringify({data: {id: 'dimmer_wall_switch'}, status: 'error', error: `Device 'dimmer_wall_switch' does not support OTA updates`}),
|
|
|
|
{retain: false, qos: 0},
|
|
|
|
expect.any(Function),
|
2020-07-08 14:23:44 -07:00
|
|
|
);
|
2020-02-08 11:55:27 -07:00
|
|
|
});
|
2020-02-09 12:44:37 -07:00
|
|
|
|
|
|
|
it('Should refuse to check/update when already in progress', async () => {
|
|
|
|
const device = zigbeeHerdsman.devices.bulb;
|
2024-06-24 11:58:47 -07:00
|
|
|
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
2020-02-09 12:44:37 -07:00
|
|
|
mockClear(mapped);
|
|
|
|
|
|
|
|
mapped.ota.isUpdateAvailable.mockImplementationOnce(() => {
|
2024-06-24 11:58:47 -07:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
setTimeout(() => resolve(), 99999);
|
|
|
|
});
|
2020-02-09 12:44:37 -07:00
|
|
|
});
|
2024-06-24 11:58:47 -07:00
|
|
|
MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/check', 'bulb');
|
2020-02-09 12:44:37 -07:00
|
|
|
await flushPromises();
|
2024-06-24 11:58:47 -07:00
|
|
|
MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/check', 'bulb');
|
2020-02-09 12:44:37 -07:00
|
|
|
await flushPromises();
|
|
|
|
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1);
|
2021-07-05 11:46:53 -07:00
|
|
|
jest.runOnlyPendingTimers();
|
2020-02-09 12:44:37 -07:00
|
|
|
await flushPromises();
|
2020-07-08 14:23:44 -07:00
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
2020-07-13 14:00:33 -07:00
|
|
|
'zigbee2mqtt/bridge/response/device/ota_update/check',
|
2024-06-24 11:58:47 -07:00
|
|
|
stringify({data: {id: 'bulb'}, status: 'error', error: `Update or check for update already in progress for 'bulb'`}),
|
|
|
|
{retain: false, qos: 0},
|
|
|
|
expect.any(Function),
|
2020-07-08 14:23:44 -07:00
|
|
|
);
|
2020-02-09 12:44:37 -07:00
|
|
|
});
|
2020-02-13 13:10:44 -07:00
|
|
|
|
2020-07-14 13:09:11 -07:00
|
|
|
it('Shouldnt crash when read modelID before/after OTA update fails', async () => {
|
2020-02-13 13:10:44 -07:00
|
|
|
const device = zigbeeHerdsman.devices.bulb;
|
|
|
|
const endpoint = device.endpoints[0];
|
2024-06-24 11:58:47 -07:00
|
|
|
endpoint.read.mockImplementation(() => {
|
|
|
|
throw new Error('Failed!');
|
|
|
|
});
|
2020-02-13 13:10:44 -07:00
|
|
|
|
2024-06-24 11:58:47 -07:00
|
|
|
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
2020-02-13 13:10:44 -07:00
|
|
|
mockClear(mapped);
|
2024-06-24 11:58:47 -07:00
|
|
|
MQTT.events.message('zigbee2mqtt/bridge/request/device/ota_update/update', 'bulb');
|
2020-02-13 13:10:44 -07:00
|
|
|
await flushPromises();
|
2020-07-08 14:23:44 -07:00
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
2020-07-13 14:00:33 -07:00
|
|
|
'zigbee2mqtt/bridge/response/device/ota_update/update',
|
2024-06-24 11:58:47 -07:00
|
|
|
stringify({data: {id: 'bulb', from: null, to: null}, status: 'ok'}),
|
|
|
|
{retain: false, qos: 0},
|
|
|
|
expect.any(Function),
|
2020-07-08 14:23:44 -07:00
|
|
|
);
|
2020-02-13 13:10:44 -07:00
|
|
|
});
|
2020-02-16 08:00:15 -07:00
|
|
|
|
|
|
|
it('Should check for update when device requests it', async () => {
|
|
|
|
const device = zigbeeHerdsman.devices.bulb;
|
2021-07-05 11:46:53 -07:00
|
|
|
device.endpoints[0].commandResponse.mockClear();
|
2020-02-16 08:00:15 -07:00
|
|
|
const data = {imageType: 12382};
|
2024-06-24 11:58:47 -07:00
|
|
|
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
2020-02-16 08:00:15 -07:00
|
|
|
mockClear(mapped);
|
2022-12-18 14:05:16 -07:00
|
|
|
mapped.ota.isUpdateAvailable.mockReturnValueOnce({available: true, currentFileVersion: 10, otaFileVersion: 12});
|
2024-06-24 11:58:47 -07:00
|
|
|
const payload = {
|
|
|
|
data,
|
|
|
|
cluster: 'genOta',
|
|
|
|
device,
|
|
|
|
endpoint: device.getEndpoint(1),
|
|
|
|
type: 'commandQueryNextImageRequest',
|
|
|
|
linkquality: 10,
|
|
|
|
meta: {zclTransactionSequenceNumber: 10},
|
|
|
|
};
|
2020-02-20 12:01:26 -07:00
|
|
|
logger.info.mockClear();
|
2020-02-16 08:00:15 -07:00
|
|
|
await zigbeeHerdsman.events.message(payload);
|
|
|
|
await flushPromises();
|
|
|
|
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1);
|
2024-06-24 11:58:47 -07:00
|
|
|
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledWith(device, {imageType: 12382});
|
2020-02-28 15:42:59 -07:00
|
|
|
expect(logger.info).toHaveBeenCalledWith(`Update available for 'bulb'`);
|
2020-02-29 04:43:53 -07:00
|
|
|
expect(device.endpoints[0].commandResponse).toHaveBeenCalledTimes(1);
|
2024-06-24 11:58:47 -07:00
|
|
|
expect(device.endpoints[0].commandResponse).toHaveBeenCalledWith('genOta', 'queryNextImageResponse', {status: 0x98}, undefined, 10);
|
2020-02-16 08:00:15 -07:00
|
|
|
|
|
|
|
// Should not request again when device asks again after a short time
|
|
|
|
await zigbeeHerdsman.events.message(payload);
|
|
|
|
await flushPromises();
|
|
|
|
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1);
|
2020-02-20 12:01:26 -07:00
|
|
|
|
|
|
|
logger.info.mockClear();
|
2022-12-18 14:05:16 -07:00
|
|
|
mapped.ota.isUpdateAvailable.mockReturnValueOnce({available: false, currentFileVersion: 10, otaFileVersion: 10});
|
2020-02-20 12:01:26 -07:00
|
|
|
await zigbeeHerdsman.events.message(payload);
|
|
|
|
await flushPromises();
|
2020-08-02 14:09:43 -07:00
|
|
|
expect(logger.info).not.toHaveBeenCalledWith(`Update available for 'bulb'`);
|
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
|
|
|
'zigbee2mqtt/bulb',
|
2024-06-24 11:58:47 -07:00
|
|
|
stringify({update_available: true, update: {state: 'available', installed_version: 10, latest_version: 12}}),
|
|
|
|
{retain: true, qos: 0},
|
|
|
|
expect.any(Function),
|
2020-08-02 14:09:43 -07:00
|
|
|
);
|
2020-02-16 08:00:15 -07:00
|
|
|
});
|
2020-02-28 15:30:33 -07:00
|
|
|
|
2022-10-27 12:49:25 -07:00
|
|
|
it('Should respond with NO_IMAGE_AVAILABLE when update available request fails', async () => {
|
|
|
|
const device = zigbeeHerdsman.devices.bulb;
|
|
|
|
device.endpoints[0].commandResponse.mockClear();
|
|
|
|
const data = {imageType: 12382};
|
2024-06-24 11:58:47 -07:00
|
|
|
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
2022-10-27 12:49:25 -07:00
|
|
|
mockClear(mapped);
|
2024-06-24 11:58:47 -07:00
|
|
|
mapped.ota.isUpdateAvailable.mockImplementationOnce(() => {
|
|
|
|
throw new Error('Nothing to find here');
|
|
|
|
});
|
|
|
|
const payload = {
|
|
|
|
data,
|
|
|
|
cluster: 'genOta',
|
|
|
|
device,
|
|
|
|
endpoint: device.getEndpoint(1),
|
|
|
|
type: 'commandQueryNextImageRequest',
|
|
|
|
linkquality: 10,
|
|
|
|
meta: {zclTransactionSequenceNumber: 10},
|
|
|
|
};
|
2022-10-27 12:49:25 -07:00
|
|
|
logger.info.mockClear();
|
|
|
|
await zigbeeHerdsman.events.message(payload);
|
|
|
|
await flushPromises();
|
|
|
|
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1);
|
2024-06-24 11:58:47 -07:00
|
|
|
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledWith(device, {imageType: 12382});
|
2022-10-27 12:49:25 -07:00
|
|
|
expect(device.endpoints[0].commandResponse).toHaveBeenCalledTimes(1);
|
2024-06-24 11:58:47 -07:00
|
|
|
expect(device.endpoints[0].commandResponse).toHaveBeenCalledWith('genOta', 'queryNextImageResponse', {status: 0x98}, undefined, 10);
|
2022-10-27 12:49:25 -07:00
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
|
|
|
'zigbee2mqtt/bulb',
|
2024-06-24 11:58:47 -07:00
|
|
|
stringify({update_available: false, update: {state: 'idle'}}),
|
|
|
|
{retain: true, qos: 0},
|
|
|
|
expect.any(Function),
|
2022-10-27 12:49:25 -07:00
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2021-07-05 11:46:53 -07:00
|
|
|
it('Should check for update when device requests it and it is not available', async () => {
|
|
|
|
const device = zigbeeHerdsman.devices.bulb;
|
|
|
|
device.endpoints[0].commandResponse.mockClear();
|
|
|
|
const data = {imageType: 12382};
|
2024-06-24 11:58:47 -07:00
|
|
|
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
2021-07-05 11:46:53 -07:00
|
|
|
mockClear(mapped);
|
2022-12-18 14:05:16 -07:00
|
|
|
mapped.ota.isUpdateAvailable.mockReturnValueOnce({available: false, currentFileVersion: 13, otaFileVersion: 13});
|
2024-06-24 11:58:47 -07:00
|
|
|
const payload = {
|
|
|
|
data,
|
|
|
|
cluster: 'genOta',
|
|
|
|
device,
|
|
|
|
endpoint: device.getEndpoint(1),
|
|
|
|
type: 'commandQueryNextImageRequest',
|
|
|
|
linkquality: 10,
|
|
|
|
meta: {zclTransactionSequenceNumber: 10},
|
|
|
|
};
|
2021-07-05 11:46:53 -07:00
|
|
|
logger.info.mockClear();
|
|
|
|
await zigbeeHerdsman.events.message(payload);
|
|
|
|
await flushPromises();
|
|
|
|
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1);
|
2024-06-24 11:58:47 -07:00
|
|
|
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledWith(device, {imageType: 12382});
|
2021-07-05 11:46:53 -07:00
|
|
|
expect(device.endpoints[0].commandResponse).toHaveBeenCalledTimes(1);
|
2024-06-24 11:58:47 -07:00
|
|
|
expect(device.endpoints[0].commandResponse).toHaveBeenCalledWith('genOta', 'queryNextImageResponse', {status: 0x98}, undefined, 10);
|
2021-07-05 11:46:53 -07:00
|
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
|
|
|
'zigbee2mqtt/bulb',
|
2024-06-24 11:58:47 -07:00
|
|
|
stringify({update_available: false, update: {state: 'idle', installed_version: 13, latest_version: 13}}),
|
|
|
|
{retain: true, qos: 0},
|
|
|
|
expect.any(Function),
|
2021-07-05 11:46:53 -07:00
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2021-02-14 10:20:32 -07:00
|
|
|
it('Should not check for update when device requests it and disable_automatic_update_check is set to true', async () => {
|
|
|
|
settings.set(['ota', 'disable_automatic_update_check'], true);
|
|
|
|
const device = zigbeeHerdsman.devices.bulb;
|
|
|
|
const data = {imageType: 12382};
|
2024-06-24 11:58:47 -07:00
|
|
|
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
2021-02-14 10:20:32 -07:00
|
|
|
mockClear(mapped);
|
2022-12-18 14:05:16 -07:00
|
|
|
mapped.ota.isUpdateAvailable.mockReturnValueOnce({available: true, currentFileVersion: 10, otaFileVersion: 13});
|
2024-06-24 11:58:47 -07:00
|
|
|
const payload = {
|
|
|
|
data,
|
|
|
|
cluster: 'genOta',
|
|
|
|
device,
|
|
|
|
endpoint: device.getEndpoint(1),
|
|
|
|
type: 'commandQueryNextImageRequest',
|
|
|
|
linkquality: 10,
|
|
|
|
meta: {zclTransactionSequenceNumber: 10},
|
|
|
|
};
|
2021-02-14 10:20:32 -07:00
|
|
|
logger.info.mockClear();
|
|
|
|
await zigbeeHerdsman.events.message(payload);
|
|
|
|
await flushPromises();
|
|
|
|
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(0);
|
|
|
|
});
|
|
|
|
|
2020-02-28 15:30:33 -07:00
|
|
|
it('Should respond with NO_IMAGE_AVAILABLE when not supporting OTA', async () => {
|
2021-04-06 13:24:30 -07:00
|
|
|
const device = zigbeeHerdsman.devices.HGZB04D;
|
2020-02-28 15:30:33 -07:00
|
|
|
const data = {imageType: 12382};
|
2024-06-24 11:58:47 -07:00
|
|
|
const payload = {
|
|
|
|
data,
|
|
|
|
cluster: 'genOta',
|
|
|
|
device,
|
|
|
|
endpoint: device.getEndpoint(1),
|
|
|
|
type: 'commandQueryNextImageRequest',
|
|
|
|
linkquality: 10,
|
|
|
|
meta: {zclTransactionSequenceNumber: 10},
|
|
|
|
};
|
2020-02-28 15:30:33 -07:00
|
|
|
await zigbeeHerdsman.events.message(payload);
|
|
|
|
await flushPromises();
|
2020-02-29 04:43:53 -07:00
|
|
|
expect(device.endpoints[0].commandResponse).toHaveBeenCalledTimes(1);
|
2024-06-24 11:58:47 -07:00
|
|
|
expect(device.endpoints[0].commandResponse).toHaveBeenCalledWith('genOta', 'queryNextImageResponse', {status: 152}, undefined, 10);
|
2020-02-28 15:30:33 -07:00
|
|
|
});
|
2020-04-28 12:23:32 -07:00
|
|
|
|
2023-01-29 11:16:03 -07:00
|
|
|
it('Should respond with NO_IMAGE_AVAILABLE when not supporting OTA and device has no OTA endpoint to standard endpoint', async () => {
|
2021-04-06 13:24:30 -07:00
|
|
|
const device = zigbeeHerdsman.devices.SV01;
|
2020-04-28 12:23:32 -07:00
|
|
|
const data = {imageType: 12382};
|
2024-06-24 11:58:47 -07:00
|
|
|
const payload = {
|
|
|
|
data,
|
|
|
|
cluster: 'genOta',
|
|
|
|
device,
|
|
|
|
endpoint: device.getEndpoint(1),
|
|
|
|
type: 'commandQueryNextImageRequest',
|
|
|
|
linkquality: 10,
|
|
|
|
meta: {zclTransactionSequenceNumber: 10},
|
|
|
|
};
|
2020-04-28 12:23:32 -07:00
|
|
|
logger.error.mockClear();
|
|
|
|
await zigbeeHerdsman.events.message(payload);
|
|
|
|
await flushPromises();
|
2023-01-29 11:16:03 -07:00
|
|
|
expect(device.endpoints[0].commandResponse).toHaveBeenCalledTimes(1);
|
2024-06-24 11:58:47 -07:00
|
|
|
expect(device.endpoints[0].commandResponse).toHaveBeenCalledWith('genOta', 'queryNextImageResponse', {status: 152}, undefined, 10);
|
2020-04-28 12:23:32 -07:00
|
|
|
});
|
2020-07-08 14:23:44 -07:00
|
|
|
|
|
|
|
it('Legacy api: Should OTA update a device', async () => {
|
|
|
|
const device = zigbeeHerdsman.devices.bulb;
|
|
|
|
const endpoint = device.endpoints[0];
|
|
|
|
let count = 0;
|
|
|
|
endpoint.read.mockImplementation(() => {
|
|
|
|
count++;
|
2024-06-24 11:58:47 -07:00
|
|
|
return {swBuildId: count, dateCode: '2019010' + count};
|
2020-07-08 14:23:44 -07:00
|
|
|
});
|
2024-06-24 11:58:47 -07:00
|
|
|
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
2020-07-08 14:23:44 -07:00
|
|
|
mockClear(mapped);
|
|
|
|
logger.info.mockClear();
|
|
|
|
logger.error.mockClear();
|
|
|
|
device.save.mockClear();
|
2024-04-04 12:33:39 -07:00
|
|
|
mapped.ota.updateToLatest.mockImplementationOnce((a, onUpdate) => {
|
2020-07-08 14:23:44 -07:00
|
|
|
onUpdate(0, null);
|
|
|
|
onUpdate(10, 3600);
|
2022-12-18 14:05:16 -07:00
|
|
|
return 91;
|
2020-07-08 14:23:44 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
MQTT.events.message('zigbee2mqtt/bridge/ota_update/update', 'bulb');
|
|
|
|
await flushPromises();
|
|
|
|
expect(logger.info).toHaveBeenCalledWith(`Updating 'bulb' to latest firmware`);
|
|
|
|
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(0);
|
|
|
|
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(1);
|
2024-04-04 12:33:39 -07:00
|
|
|
expect(mapped.ota.updateToLatest).toHaveBeenCalledWith(device, expect.any(Function));
|
2020-07-08 14:23:44 -07:00
|
|
|
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 0.00%`);
|
2021-02-21 12:47:00 -07:00
|
|
|
expect(logger.info).toHaveBeenCalledWith(`Update of 'bulb' at 10.00%, ≈ 60 minutes remaining`);
|
2022-01-16 10:25:53 -07:00
|
|
|
expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb'`);
|
2024-06-24 11:58:47 -07:00
|
|
|
expect(logger.info).toHaveBeenCalledWith(
|
|
|
|
`Device 'bulb' was updated from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190102","softwareBuildID":2}'`,
|
|
|
|
);
|
2020-07-08 14:23:44 -07:00
|
|
|
expect(logger.error).toHaveBeenCalledTimes(0);
|
2021-12-19 10:12:16 -07:00
|
|
|
expect(device.save).toHaveBeenCalledTimes(2);
|
2024-06-24 11:58:47 -07:00
|
|
|
expect(endpoint.read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {sendPolicy: 'immediate'});
|
|
|
|
expect(endpoint.read).toHaveBeenCalledWith('genBasic', ['dateCode', 'swBuildId'], {sendPolicy: undefined});
|
2020-07-08 14:23:44 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
it('Legacy api: Should handle when OTA update fails', async () => {
|
|
|
|
const device = zigbeeHerdsman.devices.bulb;
|
|
|
|
const endpoint = device.endpoints[0];
|
2024-06-24 11:58:47 -07:00
|
|
|
endpoint.read.mockImplementation(() => {
|
|
|
|
return {swBuildId: 1, dateCode: '2019010'};
|
|
|
|
});
|
|
|
|
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
2020-07-08 14:23:44 -07:00
|
|
|
mockClear(mapped);
|
|
|
|
logger.info.mockClear();
|
|
|
|
logger.error.mockClear();
|
|
|
|
device.save.mockClear();
|
2024-04-04 12:33:39 -07:00
|
|
|
mapped.ota.updateToLatest.mockImplementationOnce((a, onUpdate) => {
|
2020-07-08 14:23:44 -07:00
|
|
|
throw new Error('Update failed');
|
|
|
|
});
|
|
|
|
|
|
|
|
MQTT.events.message('zigbee2mqtt/bridge/ota_update/update', 'bulb');
|
|
|
|
await flushPromises();
|
|
|
|
expect(logger.error).toHaveBeenCalledTimes(1);
|
|
|
|
expect(logger.error).toHaveBeenCalledWith(`Update of 'bulb' failed (Update failed)`);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Legacy api: Should be able to check if OTA update is available', async () => {
|
|
|
|
const device = zigbeeHerdsman.devices.bulb;
|
2024-06-24 11:58:47 -07:00
|
|
|
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
2020-07-08 14:23:44 -07:00
|
|
|
mockClear(mapped);
|
|
|
|
|
|
|
|
logger.info.mockClear();
|
2022-12-18 14:05:16 -07:00
|
|
|
mapped.ota.isUpdateAvailable.mockReturnValueOnce({available: false, currentFileVersion: 13, otaFileVersion: 13});
|
2020-07-08 14:23:44 -07:00
|
|
|
MQTT.events.message('zigbee2mqtt/bridge/ota_update/check', 'bulb');
|
|
|
|
await flushPromises();
|
|
|
|
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1);
|
|
|
|
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0);
|
|
|
|
expect(logger.info).toHaveBeenCalledWith(`No update available for 'bulb'`);
|
|
|
|
|
|
|
|
logger.info.mockClear();
|
2022-12-18 14:05:16 -07:00
|
|
|
mapped.ota.isUpdateAvailable.mockReturnValueOnce({available: true, currentFileVersion: 13, otaFileVersion: 15});
|
2020-07-08 14:23:44 -07:00
|
|
|
MQTT.events.message('zigbee2mqtt/bridge/ota_update/check', 'bulb');
|
|
|
|
await flushPromises();
|
|
|
|
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(2);
|
|
|
|
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0);
|
|
|
|
expect(logger.info).toHaveBeenCalledWith(`Update available for 'bulb'`);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('Legacy api: Should handle if OTA update check fails', async () => {
|
|
|
|
const device = zigbeeHerdsman.devices.bulb;
|
2024-06-24 11:58:47 -07:00
|
|
|
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
2020-07-08 14:23:44 -07:00
|
|
|
mockClear(mapped);
|
|
|
|
logger.error.mockClear();
|
2024-06-24 11:58:47 -07:00
|
|
|
mapped.ota.isUpdateAvailable.mockImplementationOnce(() => {
|
|
|
|
throw new Error('RF signals disturbed because of dogs barking');
|
|
|
|
});
|
2020-07-08 14:23:44 -07:00
|
|
|
|
|
|
|
MQTT.events.message('zigbee2mqtt/bridge/ota_update/check', 'bulb');
|
|
|
|
await flushPromises();
|
|
|
|
expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1);
|
|
|
|
expect(mapped.ota.updateToLatest).toHaveBeenCalledTimes(0);
|
2023-11-05 13:01:55 -07:00
|
|
|
expect(logger.error).toHaveBeenCalledWith(`Failed to check if update available for 'bulb' (RF signals disturbed because of dogs barking)`);
|
2020-07-08 14:23:44 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
it('Legacy api: Should not check for OTA when device does not support it', async () => {
|
2021-04-06 13:24:30 -07:00
|
|
|
MQTT.events.message('zigbee2mqtt/bridge/ota_update/check', 'dimmer_wall_switch');
|
2020-07-08 14:23:44 -07:00
|
|
|
await flushPromises();
|
2021-04-06 13:24:30 -07:00
|
|
|
expect(logger.error).toHaveBeenCalledWith(`Device 'dimmer_wall_switch' does not support OTA updates`);
|
2020-07-08 14:23:44 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
it('Legacy api: Shouldnt crash when read modelID after OTA update fails', async () => {
|
|
|
|
const device = zigbeeHerdsman.devices.bulb;
|
|
|
|
const endpoint = device.endpoints[0];
|
|
|
|
let count = 0;
|
|
|
|
endpoint.read.mockImplementation(() => {
|
2024-06-24 11:58:47 -07:00
|
|
|
if (count === 1) throw new Error('Failed!');
|
2020-07-08 14:23:44 -07:00
|
|
|
count++;
|
2024-06-24 11:58:47 -07:00
|
|
|
return {swBuildId: 1, dateCode: '2019010'};
|
2020-07-08 14:23:44 -07:00
|
|
|
});
|
|
|
|
|
2024-06-24 11:58:47 -07:00
|
|
|
const mapped = await zigbeeHerdsmanConverters.findByDevice(device);
|
2020-07-08 14:23:44 -07:00
|
|
|
mockClear(mapped);
|
|
|
|
logger.info.mockClear();
|
|
|
|
MQTT.events.message('zigbee2mqtt/bridge/ota_update/update', 'bulb');
|
|
|
|
await flushPromises();
|
|
|
|
expect(logger.info).toHaveBeenCalledWith(`Finished update of 'bulb'`);
|
|
|
|
});
|
2022-01-04 12:29:17 -07:00
|
|
|
|
|
|
|
it('Set zigbee_ota_override_index_location', async () => {
|
|
|
|
settings.set(['ota', 'zigbee_ota_override_index_location'], 'local.index.json');
|
|
|
|
await resetExtension();
|
2022-05-20 23:37:39 -07:00
|
|
|
expect(spyUseIndexOverride).toHaveBeenCalledWith(path.join(data.mockDir, 'local.index.json'));
|
2022-01-04 12:29:17 -07:00
|
|
|
spyUseIndexOverride.mockClear();
|
2024-06-24 11:58:47 -07:00
|
|
|
|
2022-01-04 12:29:17 -07:00
|
|
|
settings.set(['ota', 'zigbee_ota_override_index_location'], 'http://my.site/index.json');
|
|
|
|
await resetExtension();
|
|
|
|
expect(spyUseIndexOverride).toHaveBeenCalledWith('http://my.site/index.json');
|
|
|
|
spyUseIndexOverride.mockClear();
|
|
|
|
});
|
2024-03-05 14:33:33 -07:00
|
|
|
|
|
|
|
it('Clear update state on startup', async () => {
|
|
|
|
const device = controller.zigbee.resolveEntity(zigbeeHerdsman.devices.bulb_color.ieeeAddr);
|
2024-06-24 11:58:47 -07:00
|
|
|
controller.state.set(device, {update: {progress: 100, remaining: 10, state: 'updating'}});
|
2024-03-05 14:33:33 -07:00
|
|
|
await resetExtension();
|
|
|
|
expect(controller.state.get(device)).toStrictEqual({update: {state: 'available'}});
|
|
|
|
});
|
2020-02-08 11:55:27 -07:00
|
|
|
});
|