From 67f02adbd016f3154dd8730a5f102c6e9074dc68 Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Wed, 15 Jul 2020 23:22:32 +0200 Subject: [PATCH] Prefer blocklist/passlist over blacklist/whitelist --- lib/extension/availability.js | 25 ++++++++++++-------- lib/extension/bridge.js | 22 +++++++++--------- lib/util/settings.js | 28 +++++++++++++++++++++- lib/zigbee.js | 34 +++++++++++++++------------ test/availability.test.js | 44 ++++++++++++++++++++++++++++------- test/bridge.test.js | 18 +++++++------- test/controller.test.js | 40 +++++++++++++++++++++---------- test/settings.test.js | 14 ++++++++--- 8 files changed, 156 insertions(+), 69 deletions(-) diff --git a/lib/extension/availability.js b/lib/extension/availability.js index f0dd19799..0c135451e 100644 --- a/lib/extension/availability.js +++ b/lib/extension/availability.js @@ -24,22 +24,27 @@ class Availability extends Extension { this.timers = {}; this.state = {}; - this.blacklist = settings.get().advanced.availability_blacklist.map((e) => settings.getEntity(e).ID); - this.whitelist = settings.get().advanced.availability_whitelist.map((e) => settings.getEntity(e).ID); + this.blocklist = settings.get().advanced.availability_blocklist + .concat(settings.get().advanced.availability_blacklist) + .map((e) => settings.getEntity(e).ID); + + this.passlist = settings.get().advanced.availability_passlist + .concat(settings.get().advanced.availability_whitelist) + .map((e) => settings.getEntity(e).ID); } - inWhitelistOrNotInBlacklist(device) { + inPasslistOrNotInBlocklist(device) { const ieeeAddr = device.ieeeAddr; const deviceSettings = settings.getDevice(ieeeAddr); const name = deviceSettings.friendlyName; - // Whitelist is not empty and device is in it, enable availability - if (this.whitelist.length > 0) { - return this.whitelist.includes(ieeeAddr) || (name && this.whitelist.includes(name)); + // Passlist is not empty and device is in it, enable availability + if (this.passlist.length > 0) { + return this.passlist.includes(ieeeAddr) || (name && this.passlist.includes(name)); } - // Device is on blacklist, disable availability - if (this.blacklist.includes(ieeeAddr) || (name && this.blacklist.includes(name))) { + // Device is on blocklist, disable availability + if (this.blocklist.includes(ieeeAddr) || (name && this.blocklist.includes(name))) { return false; } @@ -61,7 +66,7 @@ class Availability extends Extension { const ieeeAddr = device.ieeeAddr; this.publishAvailability(device, this.state.hasOwnProperty(ieeeAddr) ? this.state[ieeeAddr] : true, true); - if (this.inWhitelistOrNotInBlacklist(device)) { + if (this.inPasslistOrNotInBlocklist(device)) { if (this.isPingable(device)) { this.setTimerPingable(device); } else { @@ -166,7 +171,7 @@ class Availability extends Extension { return; } - if (this.inWhitelistOrNotInBlacklist(device)) { + if (this.inPasslistOrNotInBlocklist(device)) { this.publishAvailability(data.device, true); if (this.isPingable(device)) { diff --git a/lib/extension/bridge.js b/lib/extension/bridge.js index 55103c850..1658b2db8 100644 --- a/lib/extension/bridge.js +++ b/lib/extension/bridge.js @@ -250,24 +250,24 @@ class Bridge extends Extension { const ID = typeof message === 'object' ? message.id : message.trim(); const entity = this.getEntity(entityType, ID); - let ban = false; + let block = false; let force = false; - let banForceLog = ''; + let blockForceLog = ''; if (entityType === 'device' && typeof message === 'object') { - ban = !!message.ban; + block = !!message.block; force = !!message.force; - banForceLog = ` (ban: ${ban}, force: ${force})`; + blockForceLog = ` (block: ${block}, force: ${force})`; } else if (entityType === 'group' && typeof message === 'object') { force = !!message.force; - banForceLog = ` (force: ${force})`; + blockForceLog = ` (force: ${force})`; } try { - logger.info(`Removing ${entity.type} '${entity.settings.friendlyName}'${banForceLog}`); + logger.info(`Removing ${entity.type} '${entity.settings.friendlyName}'${blockForceLog}`); if (entity.type === 'device') { - if (ban) { - settings.banDevice(entity.settings.ID); + if (block) { + settings.blockDevice(entity.settings.ID); } if (force) { @@ -298,18 +298,18 @@ class Bridge extends Extension { // Remove from state this.state.remove(entity.settings.ID); - logger.info(`Successfully removed ${entity.type} '${entity.settings.friendlyName}'${banForceLog}`); + logger.info(`Successfully removed ${entity.type} '${entity.settings.friendlyName}'${blockForceLog}`); if (entity.type === 'device') { this.publishDevices(); - return utils.getResponse(message, {id: ID, ban: ban, force: force}, null); + return utils.getResponse(message, {id: ID, block, force}, null); } else { this.publishGroups(); return utils.getResponse(message, {id: ID, force: force}, null); } } catch (error) { throw new Error( - `Failed to remove ${entity.type} '${entity.settings.friendlyName}'${banForceLog} (${error})`, + `Failed to remove ${entity.type} '${entity.settings.friendlyName}'${blockForceLog} (${error})`, ); } } diff --git a/lib/util/settings.js b/lib/util/settings.js index cf197a589..7c015a890 100644 --- a/lib/util/settings.js +++ b/lib/util/settings.js @@ -8,6 +8,9 @@ const Ajv = require('ajv'); const ajv = new Ajv({allErrors: true}); const defaults = { + passlist: [], + blocklist: [], + // Deprecated: use block/passlist whitelist: [], ban: [], permit_join: false, @@ -59,6 +62,9 @@ const defaults = { // Availability timeout in seconds, disabled by default. availability_timeout: 0, + availability_blocklist: [], + availability_passlist: [], + // Deprecated, use block/passlist availability_blacklist: [], availability_whitelist: [], @@ -157,8 +163,11 @@ const schema = { adapter: {type: 'string', enum: ['deconz', 'zstack']}, }, }, - ban: {type: 'array', items: {type: 'string'}}, + blocklist: {type: 'array', items: {type: 'string'}}, + passlist: {type: 'array', items: {type: 'string'}}, + // Deprecated: use block/passlist whitelist: {type: 'array', items: {type: 'string'}}, + ban: {type: 'array', items: {type: 'string'}}, experimental: { type: 'object', properties: { @@ -187,6 +196,9 @@ const schema = { last_seen: {type: 'string', enum: ['disable', 'ISO_8601', 'ISO_8601_local', 'epoch']}, elapsed: {type: 'boolean'}, availability_timeout: {type: 'number', minimum: 0}, + availability_blocklist: {type: 'array', items: {type: 'string'}}, + availability_passlist: {type: 'array', items: {type: 'string'}}, + // Deprecated, use block/passlist availability_blacklist: {type: 'array', items: {type: 'string'}}, availability_whitelist: {type: 'array', items: {type: 'string'}}, report: {type: 'boolean'}, @@ -363,6 +375,8 @@ function validate() { checkAvailabilityList(settingsWithDefaults.advanced.availability_blacklist, 'availability_blacklist'); checkAvailabilityList(settingsWithDefaults.advanced.availability_whitelist, 'availability_whitelist'); + checkAvailabilityList(settingsWithDefaults.advanced.availability_blocklist, 'availability_blocklist'); + checkAvailabilityList(settingsWithDefaults.advanced.availability_passlist, 'availability_passlist'); return !valid ? validate.errors.map((v) => `${v.dataPath.substring(1)} ${v.message}`) : null; } @@ -536,6 +550,7 @@ function addDevice(ID) { return getDevice(ID); } +// Legacy: can be removed after bridgeLegacy has been removed function whitelistDevice(ID) { const settings = get(); if (!settings.whitelist) { @@ -550,6 +565,16 @@ function whitelistDevice(ID) { write(); } +function blockDevice(ID) { + const settings = get(); + if (!settings.blocklist) { + settings.blocklist = []; + } + + settings.blocklist.push(ID); + write(); +} + function banDevice(ID) { const settings = get(); if (!settings.ban) { @@ -687,6 +712,7 @@ module.exports = { getEntity, whitelistDevice, banDevice, + blockDevice, addDevice, removeDevice, addGroup, diff --git a/lib/zigbee.js b/lib/zigbee.js index 06908b394..e1d818754 100644 --- a/lib/zigbee.js +++ b/lib/zigbee.js @@ -71,14 +71,16 @@ class Zigbee extends events.EventEmitter { logger.debug(`Zigbee network parameters: ${JSON.stringify(await this.herdsman.getNetworkParameters())}`); for (const device of this.getClients()) { - // If a whitelist is used, all other device will be removed from the network. - if (settings.get().whitelist.length > 0) { - if (!settings.get().whitelist.includes(device.ieeeAddr)) { - logger.warn(`Blacklisted device is connected (${device.ieeeAddr}), removing...`); + // If a passlist is used, all other device will be removed from the network. + const passlist = settings.get().passlist.concat(settings.get().whitelist); + const blocklist = settings.get().blocklist.concat(settings.get().ban); + if (passlist.length > 0) { + if (!passlist.includes(device.ieeeAddr)) { + logger.warn(`Device which is not on passlist connected (${device.ieeeAddr}), removing...`); device.removeFromNetwork(); } - } else if (settings.get().ban.includes(device.ieeeAddr)) { - logger.warn(`Banned device is connected (${device.ieeeAddr}), removing...`); + } else if (blocklist.includes(device.ieeeAddr)) { + logger.warn(`Device on blocklist is connected (${device.ieeeAddr}), removing...`); device.removeFromNetwork(); } } @@ -250,21 +252,23 @@ class Zigbee extends events.EventEmitter { } acceptJoiningDeviceHandler(ieeeAddr) { - // If set whitelist devices, all other device will be rejected to join the network - if (settings.get().whitelist.length > 0) { - if (settings.get().whitelist.includes(ieeeAddr)) { - logger.info(`Accepting joining whitelisted device '${ieeeAddr}'`); + // If passlist is set, all devices not on passlist will be rejected to join the network + const passlist = settings.get().passlist.concat(settings.get().whitelist); + const blocklist = settings.get().blocklist.concat(settings.get().ban); + if (passlist.length > 0) { + if (passlist.includes(ieeeAddr)) { + logger.info(`Accepting joining device which is on passlist '${ieeeAddr}'`); return true; } else { - logger.info(`Rejecting joining non-whitelisted device '${ieeeAddr}'`); + logger.info(`Rejecting joining not in passlist device '${ieeeAddr}'`); return false; } - } else if (settings.get().ban.length > 0) { - if (settings.get().ban.includes(ieeeAddr)) { - logger.info(`Rejecting joining banned device '${ieeeAddr}'`); + } else if (blocklist.length > 0) { + if (blocklist.includes(ieeeAddr)) { + logger.info(`Rejecting joining device which is on blocklist '${ieeeAddr}'`); return false; } else { - logger.info(`Accepting joining non-banned device '${ieeeAddr}'`); + logger.info(`Accepting joining not in blocklist device '${ieeeAddr}'`); return true; } } else { diff --git a/test/availability.test.js b/test/availability.test.js index ab9432dc6..4b96a412b 100644 --- a/test/availability.test.js +++ b/test/availability.test.js @@ -197,7 +197,21 @@ describe('Availability', () => { expect(MQTT.publish.mock.calls.find((c) => c[0].includes('availability'))).toBeUndefined(); }); - it('Should not ping devices blacklisted by friendly name', async () => { + it('Should not ping devices on blocklist by friendly name', async () => { + const device = zigbeeHerdsman.devices.bulb_color; + settings.set(['advanced', 'availability_blocklist'], ['bulb_color']) + await controller.stop(); + await flushPromises(); + controller = new Controller(); + await controller.start(); + await flushPromises(); + device.ping.mockClear(); + jest.advanceTimersByTime(11 * 1000); + await flushPromises(); + expect(device.ping).toHaveBeenCalledTimes(0); + }); + + it('Should not ping devices on blacklist by friendly name', async () => { const device = zigbeeHerdsman.devices.bulb_color; settings.set(['advanced', 'availability_blacklist'], ['bulb_color']) await controller.stop(); @@ -211,9 +225,9 @@ describe('Availability', () => { expect(device.ping).toHaveBeenCalledTimes(0); }); - it('Should not ping devices blacklisted by IEEE address', async () => { + it('Should not ping devices on blocklist by IEEE address', async () => { const device = zigbeeHerdsman.devices.bulb_color; - settings.set(['advanced', 'availability_blacklist'], [device.ieeeAddr]); + settings.set(['advanced', 'availability_blocklist'], [device.ieeeAddr]); await controller.stop(); await flushPromises(); controller = new Controller(); @@ -238,7 +252,21 @@ describe('Availability', () => { expect(device.ping).toHaveBeenCalledTimes(1); }); - it('Should ping devices whitelisted by friendly name if availability_whitelist is set', async () => { + it('Should ping devices on passlist by friendly name if availability_passlist is set', async () => { + const device = zigbeeHerdsman.devices.bulb_color; + settings.set(['advanced', 'availability_passlist'], ['bulb_color']); + await controller.stop(); + await flushPromises(); + controller = new Controller(); + await controller.start(); + await flushPromises(); + device.ping.mockClear(); + jest.advanceTimersByTime(11 * 1000); + await flushPromises(); + expect(device.ping).toHaveBeenCalledTimes(1); + }); + + it('Should ping devices on whitelist by friendly name if availability_whitelist is set', async () => { const device = zigbeeHerdsman.devices.bulb_color; settings.set(['advanced', 'availability_whitelist'], ['bulb_color']); await controller.stop(); @@ -252,9 +280,9 @@ describe('Availability', () => { expect(device.ping).toHaveBeenCalledTimes(1); }); - it('Should ping devices whitelisted by IEEE address if availability_whitelist is set', async () => { + it('Should ping devices on passlist by IEEE address if availability_passlist is set', async () => { const device = zigbeeHerdsman.devices.bulb_color; - settings.set(['advanced', 'availability_whitelist'], [device.ieeeAddr]); + settings.set(['advanced', 'availability_passlist'], [device.ieeeAddr]); await controller.stop(); await flushPromises(); controller = new Controller(); @@ -266,10 +294,10 @@ describe('Availability', () => { expect(device.ping).toHaveBeenCalledTimes(1); }); - it('Should not ping non-whitelisted devices if availability_whitelist is set', async () => { + it('Should not ping devices not in passlist if availability_passlist is set', async () => { const device = zigbeeHerdsman.devices.bulb; getExtension().state[device.ieeeAddr] = false; - settings.set(['advanced', 'availability_whitelist'], ['0x000b57fffec6a5b3']) + settings.set(['advanced', 'availability_passlist'], ['0x000b57fffec6a5b3']) await controller.stop(); await flushPromises(); controller = new Controller(); diff --git a/test/bridge.test.js b/test/bridge.test.js index aa3dba5c0..a2a2e2c58 100644 --- a/test/bridge.test.js +++ b/test/bridge.test.js @@ -233,10 +233,10 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/remove', - JSON.stringify({"data":{"id": "bulb","ban":false,"force":false},"status":"ok"}), + JSON.stringify({"data":{"id": "bulb","block":false,"force":false},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); - expect(settings.get().ban).toStrictEqual([]); + expect(settings.get().blocklist).toStrictEqual([]); }); it('Should allow to remove device by object ID', async () => { @@ -250,7 +250,7 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/remove', - JSON.stringify({"data":{"id": "bulb","ban":false,"force":false},"status":"ok"}), + JSON.stringify({"data":{"id": "bulb","block":false,"force":false},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -266,25 +266,25 @@ describe('Bridge', () => { expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/remove', - JSON.stringify({"data":{"id": "bulb","ban":false,"force":true},"status":"ok"}), + JSON.stringify({"data":{"id": "bulb","block":false,"force":true},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); - it('Should allow to ban device', async () => { + it('Should allow to block device', async () => { const device = zigbeeHerdsman.devices.bulb; MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', JSON.stringify({id: "bulb", ban: true, force: true})); + MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', JSON.stringify({id: "bulb", block: true, force: true})); await flushPromises(); expect(device.removeFromDatabase).toHaveBeenCalledTimes(1); expect(settings.getDevice('bulb')).toBeNull(); expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/devices', expect.any(String), expect.any(Object), expect.any(Function)); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/remove', - JSON.stringify({"data":{"id": "bulb","ban":true,"force":true},"status":"ok"}), + JSON.stringify({"data":{"id": "bulb","block":true,"force":true},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); - expect(settings.get().ban).toStrictEqual(["0x000b57fffec6a5b2"]); + expect(settings.get().blocklist).toStrictEqual(["0x000b57fffec6a5b2"]); }); it('Should allow to remove group', async () => { @@ -337,7 +337,7 @@ describe('Bridge', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/remove', - JSON.stringify({"data":{},"status":"error","error":"Failed to remove device 'bulb' (ban: false, force: false) (Error: device timeout)"}), + JSON.stringify({"data":{},"status":"error","error":"Failed to remove device 'bulb' (block: false, force: false) (Error: device timeout)"}), {retain: false, qos: 0}, expect.any(Function) ); }); diff --git a/test/controller.test.js b/test/controller.test.js index 9e030c86b..b0c6fcc3f 100644 --- a/test/controller.test.js +++ b/test/controller.test.js @@ -159,6 +159,14 @@ describe('Controller', () => { expect(controller.state.state).toStrictEqual({}); }); + it('Should remove device not on passlist on startup', async () => { + settings.set(['passlist'], [zigbeeHerdsman.devices.bulb_color.ieeeAddr]); + await controller.start(); + await flushPromises(); + expect(zigbeeHerdsman.devices.bulb_color.removeFromNetwork).toHaveBeenCalledTimes(0); + expect(zigbeeHerdsman.devices.bulb.removeFromNetwork).toHaveBeenCalledTimes(1); + }); + it('Should remove non whitelisted devices on startup', async () => { settings.set(['whitelist'], [zigbeeHerdsman.devices.bulb_color.ieeeAddr]); await controller.start(); @@ -167,6 +175,14 @@ describe('Controller', () => { expect(zigbeeHerdsman.devices.bulb.removeFromNetwork).toHaveBeenCalledTimes(1); }); + it('Should remove device on blocklist on startup', async () => { + settings.set(['blocklist'], [zigbeeHerdsman.devices.bulb_color.ieeeAddr]); + await controller.start(); + await flushPromises(); + expect(zigbeeHerdsman.devices.bulb_color.removeFromNetwork).toHaveBeenCalledTimes(1); + expect(zigbeeHerdsman.devices.bulb.removeFromNetwork).toHaveBeenCalledTimes(0); + }); + it('Should remove banned devices on startup', async () => { settings.set(['ban'], [zigbeeHerdsman.devices.bulb_color.ieeeAddr]); await controller.start(); @@ -292,48 +308,48 @@ describe('Controller', () => { expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/bridge/log", '{"type":"device_connected","message":{"friendly_name":"bulb"}}', {"retain": false, qos: 0}, expect.any(Function)); }); - it('acceptJoiningDeviceHandler reject banned device', async () => { + it('acceptJoiningDeviceHandler reject device on blocklist', async () => { await controller.start(); const device = zigbeeHerdsman.devices.bulb; - settings.set(['ban'], [device.ieeeAddr]); + settings.set(['blocklist'], [device.ieeeAddr]); const handler = zigbeeHerdsman.constructor.mock.calls[0][0].acceptJoiningDeviceHandler; expect(await handler(device.ieeeAddr)).toBe(false); }); - it('acceptJoiningDeviceHandler accept not banned device', async () => { + it('acceptJoiningDeviceHandler accept device not on blocklist', async () => { await controller.start(); const device = zigbeeHerdsman.devices.bulb; - settings.set(['ban'], ['123']); + settings.set(['blocklist'], ['123']); const handler = zigbeeHerdsman.constructor.mock.calls[0][0].acceptJoiningDeviceHandler; expect(await handler(device.ieeeAddr)).toBe(true); }); - it('acceptJoiningDeviceHandler accept whitelisted device', async () => { + it('acceptJoiningDeviceHandler accept device on passlist', async () => { await controller.start(); const device = zigbeeHerdsman.devices.bulb; - settings.set(['whitelist'], [device.ieeeAddr]); + settings.set(['passlist'], [device.ieeeAddr]); const handler = zigbeeHerdsman.constructor.mock.calls[0][0].acceptJoiningDeviceHandler; expect(await handler(device.ieeeAddr)).toBe(true); }); - it('acceptJoiningDeviceHandler reject non-whitelisted device', async () => { + it('acceptJoiningDeviceHandler reject device not in passlist', async () => { await controller.start(); const device = zigbeeHerdsman.devices.bulb; - settings.set(['whitelist'], ['123']); + settings.set(['passlist'], ['123']); const handler = zigbeeHerdsman.constructor.mock.calls[0][0].acceptJoiningDeviceHandler; expect(await handler(device.ieeeAddr)).toBe(false); }); - it('acceptJoiningDeviceHandler should prefer whitelist above ban', async () => { + it('acceptJoiningDeviceHandler should prefer passlist above blocklist', async () => { await controller.start(); const device = zigbeeHerdsman.devices.bulb; - settings.set(['whitelist'], [device.ieeeAddr]); - settings.set(['ban'], [device.ieeeAddr]); + settings.set(['passlist'], [device.ieeeAddr]); + settings.set(['blocklist'], [device.ieeeAddr]); const handler = zigbeeHerdsman.constructor.mock.calls[0][0].acceptJoiningDeviceHandler; expect(await handler(device.ieeeAddr)).toBe(true); }); - it('acceptJoiningDeviceHandler accept when no ban and whitelist', async () => { + it('acceptJoiningDeviceHandler accept when not on blocklist and passlist', async () => { await controller.start(); const device = zigbeeHerdsman.devices.bulb; const handler = zigbeeHerdsman.constructor.mock.calls[0][0].acceptJoiningDeviceHandler; diff --git a/test/settings.test.js b/test/settings.test.js index 8a7447a6b..642f970fd 100644 --- a/test/settings.test.js +++ b/test/settings.test.js @@ -528,17 +528,17 @@ describe('Settings', () => { }).toThrowError('MQTT retention requires protocol version 5'); }); - it('Should not allow non-existing entities in availability blacklist', () => { + it('Should not allow non-existing entities in availability_blocklist', () => { write(configurationFile, { devices: {'0x0017880104e45519': {friendly_name: 'tain'}}, - advanced: {availability_blacklist: ['0x0017880104e45519', 'non_existing']}, + advanced: {availability_blocklist: ['0x0017880104e45519', 'non_existing']}, }); settings._reRead(); expect(() => { settings.validate(); - }).toThrowError(`Non-existing entity 'non_existing' specified in 'availability_blacklist'`); + }).toThrowError(`Non-existing entity 'non_existing' specified in 'availability_blocklist'`); }); it('Should ban devices', () => { @@ -549,6 +549,14 @@ describe('Settings', () => { expect(settings.get().ban).toStrictEqual(['0x123', '0x1234']); }); + it('Should add devices to blocklist', () => { + write(configurationFile, {}); + settings.blockDevice('0x123'); + expect(settings.get().blocklist).toStrictEqual(['0x123']); + settings.blockDevice('0x1234'); + expect(settings.get().blocklist).toStrictEqual(['0x123', '0x1234']); + }); + it('Should throw error when yaml file is invalid', () => { fs.writeFileSync(configurationFile, ` good: 9