mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-10-22 14:01:20 +00:00
feat: Use Vitest for testing (#25284)
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
This commit is contained in:
1 parent
5d059b7bcb
commit
06f44533af
51 files changed
+2606
-4771
No files matched your search
+7
-3
@@ -131,10 +131,11 @@ export class Controller {
|
||||
try {
|
||||
this.sdNotify = process.env.NOTIFY_SOCKET ? await import('sd-notify') : undefined;
|
||||
logger.debug('sd-notify loaded');
|
||||
/* v8 ignore start */
|
||||
} catch {
|
||||
// istanbul ignore next
|
||||
logger.debug('sd-notify is not installed');
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
|
||||
// Start zigbee
|
||||
try {
|
||||
@@ -145,10 +146,13 @@ export class Controller {
|
||||
logger.error('Check https://www.zigbee2mqtt.io/guide/installation/20_zigbee2mqtt-fails-to-start.html for possible solutions');
|
||||
logger.error('Exiting...');
|
||||
logger.error((error as Error).stack!);
|
||||
/* istanbul ignore if */
|
||||
|
||||
/* v8 ignore start */
|
||||
if ((error as Error).message.includes('USB adapter discovery error (No valid USB adapter found)')) {
|
||||
logger.error('If this happens after updating to Zigbee2MQTT 2.0.0, see https://github.com/Koenkk/zigbee2mqtt/discussions/24364');
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
|
||||
return await this.exit(1);
|
||||
}
|
||||
|
||||
@@ -292,6 +296,7 @@ export class Controller {
|
||||
softwareBuildID: entity.zh.softwareBuildID,
|
||||
// Manufacturer name can contain \u0000, remove this.
|
||||
// https://github.com/home-assistant/core/issues/85691
|
||||
/* v8 ignore next */
|
||||
manufacturerName: entity.zh.manufacturerName?.split('\u0000')[0],
|
||||
};
|
||||
}
|
||||
@@ -360,7 +365,6 @@ export class Controller {
|
||||
try {
|
||||
await extension[method]?.();
|
||||
} catch (error) {
|
||||
/* istanbul ignore next */
|
||||
logger.error(`Failed to call '${extension.constructor.name}' '${method}' (${(error as Error).stack})`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export default class Availability extends Extension {
|
||||
|
||||
private isAvailable(entity: Device | Group): boolean {
|
||||
if (entity.isDevice()) {
|
||||
return Date.now() - (entity.zh.lastSeen ?? /* istanbul ignore next */ 0) < this.getTimeout(entity);
|
||||
return Date.now() - (entity.zh.lastSeen ?? /* v8 ignore next */ 0) < this.getTimeout(entity);
|
||||
} else {
|
||||
const membersDevices = entity.membersDevices();
|
||||
return membersDevices.length === 0 || membersDevices.some((d) => this.availabilityCache[d.ieeeAddr]);
|
||||
@@ -164,7 +164,7 @@ export default class Availability extends Extension {
|
||||
|
||||
private async publishAvailability(entity: Device | Group, logLastSeen: boolean, forcePublish = false, skipGroups = false): Promise<void> {
|
||||
if (logLastSeen && entity.isDevice()) {
|
||||
const ago = Date.now() - (entity.zh.lastSeen ?? /* istanbul ignore next */ 0);
|
||||
const ago = Date.now() - (entity.zh.lastSeen ?? /* v8 ignore next */ 0);
|
||||
|
||||
if (this.isActiveDevice(entity)) {
|
||||
logger.debug(`Active device '${entity.name}' was last seen '${(ago / utils.minutes(1)).toFixed(2)}' minutes ago.`);
|
||||
|
||||
@@ -170,12 +170,10 @@ const POLL_ON_MESSAGE: Readonly<PollOnMessage> = [
|
||||
const supportedAttrs = await getColorCapabilities(endpoint);
|
||||
const readAttrs: string[] = [];
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (supportedAttrs.colorXY) {
|
||||
readAttrs.push('currentX', 'currentY');
|
||||
}
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (supportedAttrs.colorTemperature) {
|
||||
readAttrs.push('colorTemperature');
|
||||
}
|
||||
@@ -390,7 +388,6 @@ export default class Bind extends Extension {
|
||||
failed: failedClusters,
|
||||
};
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (successfulClusters.length !== 0) {
|
||||
if (type === 'bind') {
|
||||
await this.setupReporting(
|
||||
@@ -466,7 +463,6 @@ export default class Bind extends Extension {
|
||||
const coordinatorEndpoint = this.zigbee.firstCoordinatorEndpoint();
|
||||
|
||||
for (const bind of binds) {
|
||||
/* istanbul ignore else */
|
||||
if (bind.cluster.name in REPORT_CLUSTERS) {
|
||||
for (const endpoint of this.getSetupReportingEndpoints(bind, coordinatorEndpoint)) {
|
||||
const entity = `${this.zigbee.resolveEntity(endpoint.getDevice())!.name}/${endpoint.ID}`;
|
||||
@@ -477,7 +473,6 @@ export default class Bind extends Extension {
|
||||
const items = [];
|
||||
|
||||
for (const c of REPORT_CLUSTERS[bind.cluster.name as ClusterName]!) {
|
||||
/* istanbul ignore else */
|
||||
if (!c.condition || (await c.condition(endpoint))) {
|
||||
const i = {...c};
|
||||
delete i.condition;
|
||||
@@ -524,7 +519,6 @@ export default class Bind extends Extension {
|
||||
}
|
||||
|
||||
for (const b of endpoint.binds) {
|
||||
/* istanbul ignore else */
|
||||
if (b.target === coordinator && !requiredClusters.includes(b.cluster.name) && b.cluster.name in REPORT_CLUSTERS) {
|
||||
boundClusters.push(b.cluster.name);
|
||||
}
|
||||
@@ -537,7 +531,6 @@ export default class Bind extends Extension {
|
||||
const items = [];
|
||||
|
||||
for (const item of REPORT_CLUSTERS[cluster as ClusterName]!) {
|
||||
/* istanbul ignore else */
|
||||
if (!item.condition || (await item.condition(endpoint))) {
|
||||
const i = {...item};
|
||||
delete i.condition;
|
||||
|
||||
@@ -804,7 +804,7 @@ export default class Bridge extends Extension {
|
||||
let icon = device.options.icon ?? definitionIcon;
|
||||
|
||||
if (icon) {
|
||||
/* istanbul ignore next */
|
||||
/* v8 ignore next */
|
||||
icon = icon.replace('${zigbeeModel}', utils.sanitizeImageParameter(device.zh.modelID ?? ''));
|
||||
icon = icon.replace('${model}', utils.sanitizeImageParameter(device.definition.model));
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ abstract class Extension {
|
||||
/**
|
||||
* Is called once the extension has to start
|
||||
*/
|
||||
/* istanbul ignore next */
|
||||
async start(): Promise<void> {}
|
||||
|
||||
/**
|
||||
|
||||
@@ -64,7 +64,6 @@ export default abstract class ExternalJSExtension<M> extends Extension {
|
||||
}
|
||||
|
||||
for (const fileName of fs.readdirSync(this.basePath)) {
|
||||
/* istanbul ignore else */
|
||||
if (fileName.endsWith('.js')) {
|
||||
yield {name: fileName, code: this.getFileCode(fileName)};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type {IncomingMessage, Server, ServerResponse} from 'node:http';
|
||||
import type {Socket} from 'node:net';
|
||||
|
||||
import type {RequestHandler} from 'express-static-gzip';
|
||||
|
||||
import assert from 'node:assert';
|
||||
import {existsSync, readFileSync} from 'node:fs';
|
||||
import {createServer} from 'node:http';
|
||||
@@ -9,7 +11,7 @@ import {posix} from 'node:path';
|
||||
import {parse} from 'node:url';
|
||||
|
||||
import bind from 'bind-decorator';
|
||||
import expressStaticGzip, {RequestHandler} from 'express-static-gzip';
|
||||
import expressStaticGzip from 'express-static-gzip';
|
||||
import finalhandler from 'finalhandler';
|
||||
import stringify from 'json-stable-stringify-without-jsonify';
|
||||
import WebSocket from 'ws';
|
||||
@@ -71,18 +73,19 @@ export default class Frontend extends Extension {
|
||||
}
|
||||
|
||||
override async start(): Promise<void> {
|
||||
/* istanbul ignore next */
|
||||
const options = {
|
||||
enableBrotli: true,
|
||||
// TODO: https://github.com/Koenkk/zigbee2mqtt/issues/24654 - enable compressed index serving when express-static-gzip is fixed.
|
||||
index: false,
|
||||
serveStatic: {
|
||||
index: 'index.html',
|
||||
/* v8 ignore start */
|
||||
setHeaders: (res: ServerResponse, path: string): void => {
|
||||
if (path.endsWith('index.html')) {
|
||||
res.setHeader('Cache-Control', 'no-store');
|
||||
}
|
||||
},
|
||||
/* v8 ignore stop */
|
||||
},
|
||||
};
|
||||
this.fileServer = expressStaticGzip(frontend.getPath(), options);
|
||||
@@ -172,7 +175,6 @@ export default class Frontend extends Extension {
|
||||
});
|
||||
|
||||
for (const [topic, payload] of Object.entries(this.mqtt.retainedMessages)) {
|
||||
/* istanbul ignore else */
|
||||
if (topic.startsWith(`${this.mqttBaseTopic}/`)) {
|
||||
ws.send(
|
||||
stringify({
|
||||
@@ -188,9 +190,8 @@ export default class Frontend extends Extension {
|
||||
const payload = this.state.get(device);
|
||||
const lastSeen = settings.get().advanced.last_seen;
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (lastSeen !== 'disable') {
|
||||
payload.last_seen = utils.formatDate(device.zh.lastSeen ?? 0, lastSeen);
|
||||
payload.last_seen = utils.formatDate(device.zh.lastSeen ?? /* v8 ignore next */ 0, lastSeen);
|
||||
}
|
||||
|
||||
if (device.zh.linkquality !== undefined) {
|
||||
@@ -202,14 +203,12 @@ export default class Frontend extends Extension {
|
||||
}
|
||||
|
||||
@bind private onMQTTPublishMessage(data: eventdata.MQTTMessagePublished): void {
|
||||
/* istanbul ignore else */
|
||||
if (data.topic.startsWith(`${this.mqttBaseTopic}/`)) {
|
||||
// Send topic without base_topic
|
||||
const topic = data.topic.substring(this.mqttBaseTopic.length + 1);
|
||||
const payload = utils.parseJSON(data.payload, data.payload);
|
||||
|
||||
for (const client of this.wss.clients) {
|
||||
/* istanbul ignore else */
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(stringify({topic, payload}));
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ export default class Groups extends Extension {
|
||||
if (entity instanceof Device) {
|
||||
const endpoint = entity.endpoint(endpointName);
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (endpoint) {
|
||||
for (const group of groups) {
|
||||
if (
|
||||
|
||||
@@ -360,8 +360,7 @@ class Bridge {
|
||||
constructor(ieeeAdress: string, version: zh.CoordinatorVersion, discovery: DiscoveryEntry[]) {
|
||||
this.coordinatorIeeeAddress = ieeeAdress;
|
||||
this.coordinatorType = version.type;
|
||||
/* istanbul ignore next */
|
||||
this.coordinatorFirmwareVersion = version.meta.revision ? `${version.meta.revision}` : '';
|
||||
this.coordinatorFirmwareVersion = version.meta.revision ? `${version.meta.revision}` : /* v8 ignore next */ '';
|
||||
this.discoveryEntries = discovery;
|
||||
|
||||
this.options = {
|
||||
@@ -718,11 +717,8 @@ export default class HomeAssistant extends Extension {
|
||||
},
|
||||
};
|
||||
|
||||
// istanbul ignore else
|
||||
if (tempCalibration.value_min != null) discoveryEntry.discovery_payload.min = tempCalibration.value_min;
|
||||
// istanbul ignore else
|
||||
if (tempCalibration.value_max != null) discoveryEntry.discovery_payload.max = tempCalibration.value_max;
|
||||
// istanbul ignore else
|
||||
if (tempCalibration.value_step != null) {
|
||||
discoveryEntry.discovery_payload.step = tempCalibration.value_step;
|
||||
}
|
||||
@@ -733,10 +729,10 @@ export default class HomeAssistant extends Extension {
|
||||
if (piHeatingDemand) {
|
||||
const discoveryEntry: DiscoveryEntry = {
|
||||
type: 'sensor',
|
||||
object_id: endpoint ? /* istanbul ignore next */ `${piHeatingDemand.name}_${endpoint}` : `${piHeatingDemand.name}`,
|
||||
object_id: endpoint ? /* v8 ignore next */ `${piHeatingDemand.name}_${endpoint}` : `${piHeatingDemand.name}`,
|
||||
mockProperties: [{property: piHeatingDemand.property, value: null}],
|
||||
discovery_payload: {
|
||||
name: endpoint ? /* istanbul ignore next */ `${piHeatingDemand.label} ${endpoint}` : piHeatingDemand.label,
|
||||
name: endpoint ? /* v8 ignore next */ `${piHeatingDemand.label} ${endpoint}` : piHeatingDemand.label,
|
||||
value_template: `{{ value_json.${piHeatingDemand.property} }}`,
|
||||
...(piHeatingDemand.unit && {unit_of_measurement: piHeatingDemand.unit}),
|
||||
entity_category: 'diagnostic',
|
||||
@@ -816,7 +812,6 @@ export default class HomeAssistant extends Extension {
|
||||
const closingState = motorState.values.find((s) => COVER_CLOSING_LOOKUP.includes(s.toString().toLowerCase()));
|
||||
const stoppedState = motorState.values.find((s) => COVER_STOPPED_LOOKUP.includes(s.toString().toLowerCase()));
|
||||
|
||||
// istanbul ignore else
|
||||
if (openingState && closingState && stoppedState) {
|
||||
discoveryEntry.discovery_payload.state_opening = openingState;
|
||||
discoveryEntry.discovery_payload.state_closing = closingState;
|
||||
@@ -836,10 +831,11 @@ export default class HomeAssistant extends Extension {
|
||||
discoveryEntry.discovery_payload.state_stopped = 'STOP';
|
||||
}
|
||||
|
||||
// istanbul ignore if
|
||||
/* v8 ignore start */
|
||||
if (!position && !tilt) {
|
||||
discoveryEntry.discovery_payload.optimistic = true;
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
|
||||
if (position) {
|
||||
discoveryEntry.discovery_payload = {
|
||||
@@ -879,7 +875,7 @@ export default class HomeAssistant extends Extension {
|
||||
};
|
||||
|
||||
const speed = (firstExpose as zhc.Fan).features.filter(isEnumExpose).find((e) => e.name === 'mode');
|
||||
// istanbul ignore else
|
||||
|
||||
if (speed) {
|
||||
// A fan entity in Home Assistant 2021.3 and above may have a speed,
|
||||
// controlled by a percentage from 1 to 100, and/or non-speed presets.
|
||||
@@ -943,7 +939,7 @@ export default class HomeAssistant extends Extension {
|
||||
mockProperties: [{property: firstExpose.property, value: null}],
|
||||
object_id: endpoint ? `switch_${firstExpose.name}_${endpoint}` : `switch_${firstExpose.name}`,
|
||||
discovery_payload: {
|
||||
name: endpoint ? `${firstExpose.label} ${endpoint}` : firstExpose.label,
|
||||
name: endpoint ? /* v8 ignore next */ `${firstExpose.label} ${endpoint}` : firstExpose.label,
|
||||
value_template:
|
||||
typeof firstExpose.value_on === 'boolean'
|
||||
? `{% if value_json.${firstExpose.property} %}true{% else %}false{% endif %}`
|
||||
@@ -964,7 +960,7 @@ export default class HomeAssistant extends Extension {
|
||||
object_id: endpoint ? `${firstExpose.name}_${endpoint}` : `${firstExpose.name}`,
|
||||
mockProperties: [{property: firstExpose.property, value: null}],
|
||||
discovery_payload: {
|
||||
name: endpoint ? `${firstExpose.label} ${endpoint}` : firstExpose.label,
|
||||
name: endpoint ? /* v8 ignore next */ `${firstExpose.label} ${endpoint}` : firstExpose.label,
|
||||
value_template: `{{ value_json.${firstExpose.property} }}`,
|
||||
payload_on: firstExpose.value_on,
|
||||
payload_off: firstExpose.value_off,
|
||||
@@ -1006,9 +1002,7 @@ export default class HomeAssistant extends Extension {
|
||||
delete discoveryEntry.discovery_payload.device_class;
|
||||
}
|
||||
|
||||
// istanbul ignore else
|
||||
if (firstExpose.value_min != null) discoveryEntry.discovery_payload.min = firstExpose.value_min;
|
||||
// istanbul ignore else
|
||||
if (firstExpose.value_max != null) discoveryEntry.discovery_payload.max = firstExpose.value_max;
|
||||
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
@@ -1076,7 +1070,7 @@ export default class HomeAssistant extends Extension {
|
||||
object_id: firstExpose.property,
|
||||
mockProperties: [],
|
||||
discovery_payload: {
|
||||
name: endpoint ? /* istanbul ignore next */ `${firstExpose.label} ${endpoint}` : firstExpose.label,
|
||||
name: endpoint ? /* v8 ignore next */ `${firstExpose.label} ${endpoint}` : firstExpose.label,
|
||||
state_topic: true,
|
||||
event_types: this.prepareActionEventTypes(firstExpose.values),
|
||||
value_template: this.actionValueTemplate,
|
||||
@@ -1100,7 +1094,7 @@ export default class HomeAssistant extends Extension {
|
||||
object_id: firstExpose.property,
|
||||
mockProperties: [{property: firstExpose.property, value: null}],
|
||||
discovery_payload: {
|
||||
name: endpoint ? /* istanbul ignore next */ `${firstExpose.label} ${endpoint}` : firstExpose.label,
|
||||
name: endpoint ? /* v8 ignore next */ `${firstExpose.label} ${endpoint}` : firstExpose.label,
|
||||
state_topic: false,
|
||||
command_topic_prefix: endpoint,
|
||||
command_topic: true,
|
||||
@@ -1137,7 +1131,6 @@ export default class HomeAssistant extends Extension {
|
||||
/**
|
||||
* Otherwise expose as SENSOR entity.
|
||||
*/
|
||||
/* istanbul ignore else */
|
||||
if (firstExpose.access & ACCESS_STATE) {
|
||||
discoveryEntries.push({
|
||||
type: 'sensor',
|
||||
@@ -1189,9 +1182,10 @@ export default class HomeAssistant extends Extension {
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
/* v8 ignore start */
|
||||
default:
|
||||
throw new Error(`Unsupported exposes type: '${firstExpose.type}'`);
|
||||
/* v8 ignore stop */
|
||||
}
|
||||
|
||||
// Exposes with category 'config' or 'diagnostic' are always added to the respective category.
|
||||
@@ -1249,14 +1243,16 @@ export default class HomeAssistant extends Extension {
|
||||
* zigbee2mqtt/mydevice/l1.
|
||||
*/
|
||||
const entity = this.zigbee.resolveEntity(data.entity.name)!;
|
||||
|
||||
if (entity.isDevice()) {
|
||||
for (const topic in this.getDiscovered(entity).messages) {
|
||||
const topicMatch = topic.match(this.discoveryRegexWoTopic);
|
||||
|
||||
// istanbul ignore if
|
||||
/* v8 ignore start */
|
||||
if (!topicMatch) {
|
||||
continue;
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
|
||||
const objectID = topicMatch[3];
|
||||
const lightMatch = /^light_(.*)/.exec(objectID);
|
||||
@@ -1333,7 +1329,8 @@ export default class HomeAssistant extends Extension {
|
||||
private getConfigs(entity: Device | Group | Bridge): DiscoveryEntry[] {
|
||||
const isDevice = entity.isDevice();
|
||||
const isGroup = entity.isGroup();
|
||||
/* istanbul ignore next */
|
||||
|
||||
/* v8 ignore next */
|
||||
if (!entity || (isDevice && !entity.definition)) return [];
|
||||
|
||||
let configs: DiscoveryEntry[] = [];
|
||||
@@ -1390,7 +1387,6 @@ export default class HomeAssistant extends Extension {
|
||||
},
|
||||
};
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (settings.get().advanced.last_seen.startsWith('ISO_8601')) {
|
||||
config.discovery_payload.device_class = 'timestamp';
|
||||
}
|
||||
@@ -1491,7 +1487,6 @@ export default class HomeAssistant extends Extension {
|
||||
if (payload.state_topic === undefined || payload.state_topic) {
|
||||
payload.state_topic = stateTopic;
|
||||
} else {
|
||||
/* istanbul ignore else */
|
||||
if (payload.state_topic !== undefined) {
|
||||
delete payload.state_topic;
|
||||
}
|
||||
@@ -1744,7 +1739,6 @@ export default class HomeAssistant extends Extension {
|
||||
// Device was flagged to be excluded from homeassistant discovery
|
||||
clear = clear || Boolean(entity && entity.options.homeassistant !== undefined && !entity.options.homeassistant);
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (clear) {
|
||||
logger.debug(`Clearing outdated Home Assistant config '${data.topic}'`);
|
||||
await this.mqtt.publish(topic, '', {retain: true, qos: 1}, this.discoveryTopic, false, false);
|
||||
|
||||
@@ -217,7 +217,6 @@ export default class NetworkMap extends Extension {
|
||||
logger.debug((error as Error).stack!);
|
||||
}
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (includeRoutes) {
|
||||
try {
|
||||
const result = await requestWithRetry<zh.RoutingTable>(async () => await device.zh.routingTable());
|
||||
@@ -283,7 +282,6 @@ export default class NetworkMap extends Extension {
|
||||
if (neighbor.ieeeAddr === '0x0000000000000000') {
|
||||
const neighborDevice = this.zigbee.deviceByNetworkAddress(neighbor.networkAddress);
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (neighborDevice) {
|
||||
neighbor.ieeeAddr = neighborDevice.ieeeAddr;
|
||||
}
|
||||
@@ -305,7 +303,6 @@ export default class NetworkMap extends Extension {
|
||||
|
||||
const routingTable = routingTables.get(device);
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (routingTable) {
|
||||
for (const entry of routingTable.table) {
|
||||
if (entry.status === 'ACTIVE' && entry.nextHop === neighbor.networkAddress) {
|
||||
|
||||
@@ -199,7 +199,6 @@ export default class Publish extends Extension {
|
||||
}
|
||||
|
||||
if (usedConverters[endpointOrGroupID] === undefined) usedConverters[endpointOrGroupID] = [];
|
||||
/* istanbul ignore next */
|
||||
// Match any key if the toZigbee converter defines no key.
|
||||
const converter = converters.find(
|
||||
(c) =>
|
||||
|
||||
@@ -102,7 +102,7 @@ export default class Receive extends Extension {
|
||||
}
|
||||
|
||||
@bind async onDeviceMessage(data: eventdata.DeviceMessage): Promise<void> {
|
||||
/* istanbul ignore next */
|
||||
/* v8 ignore next */
|
||||
if (!data.device) return;
|
||||
|
||||
if (!data.device.definition || data.device.zh.interviewing) {
|
||||
@@ -171,10 +171,12 @@ export default class Receive extends Extension {
|
||||
if (converted) {
|
||||
payload = {...payload, ...converted};
|
||||
}
|
||||
} catch (error) /* istanbul ignore next */ {
|
||||
/* v8 ignore start */
|
||||
} catch (error) {
|
||||
logger.error(`Exception while calling fromZigbee converter: ${(error as Error).message}}`);
|
||||
logger.debug((error as Error).stack!);
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
}
|
||||
|
||||
if (!utils.objectIsEmpty(payload)) {
|
||||
|
||||
+2
-4
@@ -39,7 +39,6 @@ export default class Device {
|
||||
|
||||
exposes(): zhc.Expose[] {
|
||||
assert(this.definition, 'Cannot retreive exposes before definition is resolved');
|
||||
/* istanbul ignore if */
|
||||
if (typeof this.definition.exposes == 'function') {
|
||||
const options: KeyValue = this.options;
|
||||
return this.definition.exposes(this.zh, options);
|
||||
@@ -81,7 +80,6 @@ export default class Device {
|
||||
return undefined;
|
||||
}
|
||||
} else {
|
||||
/* istanbul ignore next */
|
||||
if (key !== 'default') {
|
||||
return undefined;
|
||||
}
|
||||
@@ -104,7 +102,7 @@ export default class Device {
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
/* v8 ignore next */
|
||||
return epName === 'default' ? undefined : epName;
|
||||
}
|
||||
|
||||
@@ -123,7 +121,7 @@ export default class Device {
|
||||
isDevice(): this is Device {
|
||||
return true;
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
|
||||
isGroup(): this is Group {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ export default class Group {
|
||||
const definitions: zhc.Definition[] = [];
|
||||
|
||||
for (const member of this.membersDevices()) {
|
||||
/* istanbul ignore else */
|
||||
if (member.definition) {
|
||||
definitions.push(member.definition);
|
||||
}
|
||||
|
||||
@@ -191,7 +191,6 @@ export default class MQTT {
|
||||
this.eventBus.emitMQTTMessagePublished({topic, payload, options: {...defaultOptions, ...options}});
|
||||
|
||||
if (!this.isConnected()) {
|
||||
/* istanbul ignore else */
|
||||
if (!skipLog) {
|
||||
logger.error(`Not connected to MQTT server!`);
|
||||
logger.error(`Cannot send message: topic: '${topic}', payload: '${payload}`);
|
||||
@@ -213,7 +212,6 @@ export default class MQTT {
|
||||
try {
|
||||
await this.client.publishAsync(topic, payload, actualOptions);
|
||||
} catch (error) {
|
||||
/* istanbul ignore else */
|
||||
if (!skipLog) {
|
||||
logger.error(`MQTT server error: ${(error as Error).message}`);
|
||||
logger.error(`Could not send message: topic: '${topic}', payload: '${payload}`);
|
||||
|
||||
+14
-13
@@ -59,11 +59,9 @@ class Logger {
|
||||
// winston.config.syslog.levels sets 'warning' as 'red'
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize({colors: {debug: 'blue', info: 'green', warning: 'yellow', error: 'red'}}),
|
||||
winston.format.printf(
|
||||
/* istanbul ignore next */ (info) => {
|
||||
return `[${info.timestamp}] ${info.level}: \t${info.message}`;
|
||||
},
|
||||
),
|
||||
winston.format.printf((info) => {
|
||||
return `[${info.timestamp}] ${info.level}: \t${info.message}`;
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
@@ -77,10 +75,13 @@ class Logger {
|
||||
if (settings.get().advanced.log_symlink_current) {
|
||||
const current = settings.get().advanced.log_directory.replace('%TIMESTAMP%', 'current');
|
||||
const actual = './' + timestamp;
|
||||
/* istanbul ignore next */
|
||||
|
||||
/* v8 ignore start */
|
||||
if (fs.existsSync(current)) {
|
||||
fs.unlinkSync(current);
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
|
||||
fs.symlinkSync(actual, current);
|
||||
}
|
||||
|
||||
@@ -88,11 +89,9 @@ class Logger {
|
||||
// NOTE: the initiation of the logger even when not added as transport tries to create the logging directory
|
||||
const transportFileOptions: winston.transports.FileTransportOptions = {
|
||||
filename: path.join(this.directory, logFilename),
|
||||
format: winston.format.printf(
|
||||
/* istanbul ignore next */ (info) => {
|
||||
return `[${info.timestamp}] ${info.level}: \t${info.message}`;
|
||||
},
|
||||
),
|
||||
format: winston.format.printf((info) => {
|
||||
return `[${info.timestamp}] ${info.level}: \t${info.message}`;
|
||||
}),
|
||||
};
|
||||
|
||||
if (settings.get().advanced.log_rotation) {
|
||||
@@ -106,7 +105,7 @@ class Logger {
|
||||
this.cleanup();
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
/* v8 ignore start */
|
||||
if (this.output.includes('syslog')) {
|
||||
logging += `, syslog`;
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unused-expressions
|
||||
@@ -125,6 +124,7 @@ class Logger {
|
||||
// @ts-expect-error untyped transport
|
||||
this.logger.add(new winston.transports.Syslog(options));
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
|
||||
this.setDebugNamespaceIgnore(settings.get().advanced.log_debug_namespace_ignore);
|
||||
|
||||
@@ -240,7 +240,7 @@ class Logger {
|
||||
|
||||
// Workaround for https://github.com/winstonjs/winston/issues/1629.
|
||||
// https://github.com/Koenkk/zigbee2mqtt/pull/10905
|
||||
/* istanbul ignore next */
|
||||
/* v8 ignore start */
|
||||
public async end(): Promise<void> {
|
||||
this.logger.end();
|
||||
|
||||
@@ -259,6 +259,7 @@ class Logger {
|
||||
}
|
||||
});
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
}
|
||||
|
||||
export default new Logger();
|
||||
@@ -87,7 +87,7 @@ export const defaults: RecursivePartial<Settings> = {
|
||||
log_output: ['console', 'file'],
|
||||
log_directory: path.join(data.getPath(), 'log', '%TIMESTAMP%'),
|
||||
log_file: 'log.log',
|
||||
log_level: /* istanbul ignore next */ process.env.DEBUG ? 'debug' : 'info',
|
||||
log_level: /* v8 ignore next */ process.env.DEBUG ? 'debug' : 'info',
|
||||
log_namespaced_levels: {},
|
||||
log_syslog: {},
|
||||
log_debug_to_mqtt_frontend: false,
|
||||
@@ -353,7 +353,6 @@ function applyEnvironmentVariables(settings: Partial<Settings>): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
setting[key as keyof Settings] = (envVariable.toLowerCase() === 'true') as any;
|
||||
} else {
|
||||
/* istanbul ignore else */
|
||||
if (type.indexOf('string') >= 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
setting[key as keyof Settings] = envVariable as any;
|
||||
|
||||
@@ -54,14 +54,15 @@ function setValue(currentSettings: any, path: string[], value: unknown, createPa
|
||||
currentSettings[key] = value;
|
||||
} else {
|
||||
if (!currentSettings[key]) {
|
||||
/* istanbul ignore else */
|
||||
if (createPathIfNotExist) {
|
||||
currentSettings[key] = {};
|
||||
/* v8 ignore start */
|
||||
} else {
|
||||
// invalid path
|
||||
// ignored in test since currently call is always guarded by get-validated path, so this is never reached
|
||||
return false;
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
}
|
||||
|
||||
currentSettings = currentSettings[key];
|
||||
@@ -407,7 +408,6 @@ function migrateToThree(
|
||||
const changeToObject = (currentSettings: Partial<Settings>, path: string[]): ReturnType<SettingsCustomHandler['execute']> => {
|
||||
const [validPath, previousValue] = getValue(currentSettings, path);
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (validPath) {
|
||||
if (typeof previousValue === 'boolean') {
|
||||
setValue(currentSettings, path, {enabled: previousValue});
|
||||
@@ -456,8 +456,8 @@ export function migrateIfNecessary(): void {
|
||||
);
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
const finalVersion = process.env.JEST_WORKER_ID ? settings.testing.CURRENT_VERSION : settings.CURRENT_VERSION;
|
||||
/* v8 ignore next */
|
||||
const finalVersion = process.env.VITEST_WORKER_ID ? settings.testing.CURRENT_VERSION : settings.CURRENT_VERSION;
|
||||
|
||||
// when same version as current, nothing left to do
|
||||
while (currentSettings.version !== finalVersion) {
|
||||
@@ -473,7 +473,6 @@ export function migrateIfNecessary(): void {
|
||||
backupSettings(currentSettings.version || 1);
|
||||
|
||||
// each version should only bump to the next version so as to gradually migrate if necessary
|
||||
/* istanbul ignore else */
|
||||
if (currentSettings.version == undefined) {
|
||||
// migrating from 1 (`version` did not exist) to 2
|
||||
migrationNotesFileName = 'migration-1-to-2.log';
|
||||
@@ -518,13 +517,11 @@ export function migrateIfNecessary(): void {
|
||||
for (const customHandler of customHandlers) {
|
||||
const [validPath, previousValue, changed] = customHandler.execute(currentSettings);
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (validPath && changed && (!customHandler.noteIf || customHandler.noteIf(previousValue))) {
|
||||
migrationNotes.add(`[SPECIAL] ${customHandler.note}`);
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (migrationNotesFileName && migrationNotes.size > 0) {
|
||||
migrationNotes.add(`For more details, see https://github.com/Koenkk/zigbee2mqtt/discussions/24198`);
|
||||
const migrationNotesFilePath = data.joinPath(migrationNotesFileName);
|
||||
|
||||
+3
-2
@@ -61,10 +61,11 @@ async function getZigbee2MQTTVersion(includeCommitHash = true): Promise<{commitH
|
||||
if (err) {
|
||||
try {
|
||||
commitHash = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', '.hash'), 'utf-8');
|
||||
/* v8 ignore start */
|
||||
} catch {
|
||||
/* istanbul ignore next */
|
||||
commitHash = 'unknown';
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
} else {
|
||||
commitHash = commit.shortHash;
|
||||
}
|
||||
@@ -369,7 +370,7 @@ function deviceNotCoordinator(device: zh.Device): boolean {
|
||||
return device.type !== 'Coordinator';
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
/* v8 ignore next */
|
||||
const noop = (): void => {};
|
||||
|
||||
export default {
|
||||
|
||||
+2
-3
@@ -92,7 +92,7 @@ export default class Zigbee {
|
||||
});
|
||||
this.herdsman.on('deviceInterview', async (data: ZHEvents.DeviceInterviewPayload) => {
|
||||
const device = this.resolveDevice(data.device.ieeeAddr);
|
||||
/* istanbul ignore if */ if (!device) return; // Prevent potential race
|
||||
/* v8 ignore next */ if (!device) return; // Prevent potential race
|
||||
await device.resolveDefinition();
|
||||
const d = {device, status: data.status};
|
||||
this.logDeviceInterview(d);
|
||||
@@ -100,7 +100,7 @@ export default class Zigbee {
|
||||
});
|
||||
this.herdsman.on('deviceJoined', async (data: ZHEvents.DeviceJoinedPayload) => {
|
||||
const device = this.resolveDevice(data.device.ieeeAddr);
|
||||
/* istanbul ignore if */ if (!device) return; // Prevent potential race
|
||||
/* v8 ignore next */ if (!device) return; // Prevent potential race
|
||||
await device.resolveDefinition();
|
||||
logger.info(`Device '${device.name}' joined`);
|
||||
this.eventBus.emitDeviceJoined({device});
|
||||
@@ -309,7 +309,6 @@ export default class Zigbee {
|
||||
// First split the input token by the latest slash
|
||||
const match = ID.match(entityIDRegex);
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (match) {
|
||||
// Get the resulting IDs from the match
|
||||
entityName = match[1];
|
||||
|
||||
Reference in new issue
Block a user