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
2022-05-20 23:37:39 -07:00
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' ) ;
}
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 ( ) ;
}
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-03 02:51:05 -07:00
it ( 'onlythis 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 ++ ;
return { swBuildId : count , dateCode : '2019010' + count }
} ) ;
2023-12-25 04:46:57 -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 ( ) ;
2020-02-08 11:55:27 -07:00
mapped . ota . updateToLatest . mockImplementationOnce ( ( a , b , 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 ) ;
expect ( mapped . ota . updateToLatest ) . toHaveBeenCalledWith ( device , logger , 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' ` ) ;
expect ( logger . info ) . toHaveBeenCalledWith ( ` Device 'bulb' was updated from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190103","softwareBuildID":3}' ` ) ;
2021-12-19 10:12:16 -07:00
expect ( device . save ) . toHaveBeenCalledTimes ( 2 ) ;
2022-01-16 10:25:53 -07:00
expect ( endpoint . read ) . toHaveBeenCalledWith ( 'genBasic' , [ 'dateCode' , 'swBuildId' ] , { 'sendWhen' : 'immediate' } ) ;
expect ( endpoint . read ) . toHaveBeenCalledWith ( 'genBasic' , [ 'dateCode' , 'swBuildId' ] , { 'sendWhen' : 'active' } ) ;
2020-08-02 14:09:43 -07:00
expect ( MQTT . publish ) . toHaveBeenCalledWith (
'zigbee2mqtt/bulb' ,
2020-08-13 11:00:35 -07:00
stringify ( { "update_available" : false , "update" : { "state" : "updating" , "progress" : 0 } } ) ,
2020-08-02 14:09:43 -07:00
{ retain : true , qos : 0 } , expect . any ( Function )
) ;
expect ( MQTT . publish ) . toHaveBeenCalledWith (
'zigbee2mqtt/bulb' ,
2020-08-13 11:00:35 -07:00
stringify ( { "update_available" : false , "update" : { "state" : "updating" , "progress" : 10 , "remaining" : 3600 } } ) ,
2020-08-02 14:09:43 -07:00
{ retain : true , qos : 0 } , expect . any ( Function )
) ;
expect ( MQTT . publish ) . toHaveBeenCalledWith (
'zigbee2mqtt/bulb' ,
2022-12-18 14:05:16 -07:00
stringify ( { "update_available" : false , "update" : { "state" : "idle" , "installed_version" : 90 , "latest_version" : 90 } } ) ,
2020-08-02 14:09:43 -07:00
{ retain : true , qos : 0 } , expect . any ( Function )
) ;
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' ,
2022-01-16 10:25:53 -07:00
stringify ( { "data" : { "from" : { "date_code" : "20190101" , "software_build_id" : 1 } , "id" : "bulb" , "to" : { "date_code" : "20190103" , "software_build_id" : 3 } } , "status" : "ok" } ) ,
2020-07-08 14:23:44 -07:00
{ retain : false , qos : 0 } , expect . any ( Function )
2020-09-15 13:13:30 -07:00
) ;
expect ( MQTT . publish ) . toHaveBeenCalledWith (
'zigbee2mqtt/bridge/devices' ,
expect . any ( String ) ,
{ retain : true , 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 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 ] ;
endpoint . read . mockImplementation ( ( ) => { return { swBuildId : 1 , dateCode : '2019010' } } ) ;
2023-12-25 04:46:57 -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
device . save . mockClear ( ) ;
mapped . ota . updateToLatest . mockImplementationOnce ( ( a , b , onUpdate ) => {
throw new Error ( 'Update failed' ) ;
} ) ;
2020-02-08 11:55:27 -07:00
2020-08-13 11:00:35 -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' ,
2020-08-13 11:00:35 -07:00
stringify ( { "update_available" : true , "update" : { "state" : "available" } } ) ,
2020-08-02 14:09:43 -07:00
{ retain : true , qos : 0 } , expect . any ( Function )
) ;
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' ,
2020-08-13 11:00:35 -07:00
stringify ( { "data" : { "id" : "bulb" } , "status" : "error" , "error" : "Update of 'bulb' failed (Update failed)" } ) ,
2020-07-08 14:23:44 -07:00
{ retain : false , qos : 0 } , expect . any ( Function )
) ;
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 ;
2023-12-25 04:46:57 -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 } ) ;
2020-07-13 14:00:33 -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' ,
2020-08-13 11:00:35 -07:00
stringify ( { "data" : { "id" : "bulb" , "updateAvailable" : false } , "status" : "ok" } ) ,
2020-07-08 14:23:44 -07:00
{ retain : false , qos : 0 } , expect . any ( Function )
) ;
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 } ) ;
2020-07-13 14:00:33 -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' ,
2020-08-13 11:00:35 -07:00
stringify ( { "data" : { "id" : "bulb" , "updateAvailable" : true } , "status" : "ok" } ) ,
2020-07-08 14:23:44 -07:00
{ retain : false , qos : 0 } , expect . any ( Function )
) ;
2020-02-09 12:44:37 -07:00
} ) ;
it ( 'Should handle if OTA update check fails' , async ( ) => {
const device = zigbeeHerdsman . devices . bulb ;
2023-12-25 04:46:57 -07:00
const mapped = await zigbeeHerdsmanConverters . findByDevice ( device )
2020-02-09 12:44:37 -07:00
mockClear ( mapped ) ;
2023-11-05 13:01:55 -07:00
mapped . ota . isUpdateAvailable . mockImplementationOnce ( ( ) => { throw new Error ( 'RF signals disturbed because of dogs barking' ) } ) ;
2020-02-09 12:44:37 -07:00
2020-07-13 14:00:33 -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' ,
2023-11-05 13:01:55 -07:00
stringify ( { "data" : { "id" : "bulb" } , "status" : "error" , "error" : ` Failed to check if update available for 'bulb' (RF signals disturbed because of dogs barking) ` } ) ,
2020-07-08 14:23:44 -07:00
{ retain : false , qos : 0 } , expect . any ( Function )
) ;
} ) ;
it ( 'Should fail when device does not exist' , async ( ) => {
2020-07-13 14:00:33 -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' ,
2020-08-13 11:00:35 -07:00
stringify ( { "data" : { "id" : "not_existing_deviceooo" } , "status" : "error" , "error" : ` Device 'not_existing_deviceooo' does not exist ` } ) ,
2020-07-08 14:23:44 -07:00
{ retain : false , qos : 0 } , expect . any ( Function )
) ;
2020-02-08 11:55:27 -07:00
} ) ;
it ( '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/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' ,
2021-04-06 13:24:30 -07:00
stringify ( { "data" : { "id" : "dimmer_wall_switch" } , "status" : "error" , "error" : ` Device 'dimmer_wall_switch' does not support OTA updates ` } ) ,
2020-07-08 14:23:44 -07:00
{ retain : false , qos : 0 } , expect . any ( Function )
) ;
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 ;
2023-12-25 04:46:57 -07:00
const mapped = await zigbeeHerdsmanConverters . findByDevice ( device )
2020-02-09 12:44:37 -07:00
mockClear ( mapped ) ;
mapped . ota . isUpdateAvailable . mockImplementationOnce ( ( ) => {
return new Promise ( ( resolve , reject ) => { setTimeout ( ( ) => resolve ( ) , 99999 ) } )
} ) ;
2020-07-13 14:00:33 -07:00
MQTT . events . message ( 'zigbee2mqtt/bridge/request/device/ota_update/check' , "bulb" ) ;
2020-02-09 12:44:37 -07:00
await flushPromises ( ) ;
2020-07-13 14:00:33 -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' ,
2020-08-13 11:00:35 -07:00
stringify ( { "data" : { "id" : "bulb" } , "status" : "error" , "error" : ` Update or check for update already in progress for 'bulb' ` } ) ,
2020-07-08 14:23:44 -07:00
{ retain : false , qos : 0 } , expect . any ( Function )
) ;
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 ] ;
2020-07-14 13:09:11 -07:00
endpoint . read . mockImplementation ( ( ) => { throw new Error ( 'Failed!' ) } ) ;
2020-02-13 13:10:44 -07:00
2023-12-25 04:46:57 -07:00
const mapped = await zigbeeHerdsmanConverters . findByDevice ( device )
2020-02-13 13:10:44 -07:00
mockClear ( mapped ) ;
2020-07-13 14:00:33 -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' ,
2020-08-13 11:00:35 -07:00
stringify ( { "data" : { "id" : "bulb" , "from" : null , "to" : null } , "status" : "ok" } ) ,
2020-07-08 14:23:44 -07:00
{ retain : false , qos : 0 } , expect . any ( Function )
) ;
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 } ;
2023-12-25 04:46:57 -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 } ) ;
2023-10-06 23:55:16 -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 ) ;
expect ( mapped . ota . isUpdateAvailable ) . toHaveBeenCalledWith ( device , logger , { "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 ) ;
2023-10-06 23:55:16 -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' ,
2022-12-18 14:05:16 -07:00
stringify ( { "update_available" : true , "update" : { "state" : "available" , "installed_version" : 10 , "latest_version" : 12 } } ) ,
2020-08-02 14:09:43 -07:00
{ retain : true , qos : 0 } , expect . any ( Function )
) ;
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 } ;
2023-12-25 04:46:57 -07:00
const mapped = await zigbeeHerdsmanConverters . findByDevice ( device )
2022-10-27 12:49:25 -07:00
mockClear ( mapped ) ;
mapped . ota . isUpdateAvailable . mockImplementationOnce ( ( ) => { throw new Error ( 'Nothing to find here' ) } )
2023-10-06 23:55:16 -07:00
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 ) ;
expect ( mapped . ota . isUpdateAvailable ) . toHaveBeenCalledWith ( device , logger , { "imageType" : 12382 } ) ;
expect ( device . endpoints [ 0 ] . commandResponse ) . toHaveBeenCalledTimes ( 1 ) ;
2023-10-06 23:55:16 -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' ,
stringify ( { "update_available" : false , "update" : { "state" : "idle" } } ) ,
{ retain : true , qos : 0 } , expect . any ( Function )
) ;
} ) ;
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 } ;
2023-12-25 04:46:57 -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 } ) ;
2023-10-06 23:55:16 -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 ) ;
expect ( mapped . ota . isUpdateAvailable ) . toHaveBeenCalledWith ( device , logger , { "imageType" : 12382 } ) ;
expect ( device . endpoints [ 0 ] . commandResponse ) . toHaveBeenCalledTimes ( 1 ) ;
2023-10-06 23:55:16 -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' ,
2022-12-18 14:05:16 -07:00
stringify ( { "update_available" : false , "update" : { "state" : "idle" , "installed_version" : 13 , "latest_version" : 13 } } ) ,
2021-07-05 11:46:53 -07:00
{ retain : true , qos : 0 } , expect . any ( Function )
) ;
} ) ;
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 } ;
2023-12-25 04:46:57 -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 } ) ;
2023-10-06 23:55:16 -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 } ;
2023-10-06 23:55:16 -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 ) ;
2023-10-06 23:55:16 -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 } ;
2023-10-06 23:55:16 -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 ) ;
2023-10-06 23:55:16 -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 ++ ;
return { swBuildId : count , dateCode : '2019010' + count }
} ) ;
2023-12-25 04:46:57 -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 ( ) ;
mapped . ota . updateToLatest . mockImplementationOnce ( ( a , b , onUpdate ) => {
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 ) ;
expect ( mapped . ota . updateToLatest ) . toHaveBeenCalledWith ( device , logger , expect . any ( Function ) ) ;
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' ` ) ;
2023-11-05 13:01:55 -07:00
expect ( logger . info ) . toHaveBeenCalledWith ( ` Device 'bulb' was updated from '{"dateCode":"20190101","softwareBuildID":1}' to '{"dateCode":"20190103","softwareBuildID":3}' ` ) ;
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 ) ;
2022-01-16 10:25:53 -07:00
expect ( endpoint . read ) . toHaveBeenCalledWith ( 'genBasic' , [ 'dateCode' , 'swBuildId' ] , { 'sendWhen' : 'immediate' } ) ;
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 ] ;
endpoint . read . mockImplementation ( ( ) => { return { swBuildId : 1 , dateCode : '2019010' } } ) ;
2023-12-25 04:46:57 -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 ( ) ;
mapped . ota . updateToLatest . mockImplementationOnce ( ( a , b , onUpdate ) => {
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 ;
2023-12-25 04:46:57 -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 ;
2023-12-25 04:46:57 -07:00
const mapped = await zigbeeHerdsmanConverters . findByDevice ( device )
2020-07-08 14:23:44 -07:00
mockClear ( mapped ) ;
logger . error . mockClear ( ) ;
2023-11-05 13:01:55 -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 ( ( ) => {
if ( count === 1 ) throw new Error ( 'Failed!' )
count ++ ;
return { swBuildId : 1 , dateCode : '2019010' }
} ) ;
2023-12-25 04:46:57 -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 ( ) ;
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 ( ) ;
} ) ;
2020-02-08 11:55:27 -07:00
} ) ;