UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

423 lines (396 loc) 12.2 kB
import { NoOperationCC } from "@zwave-js/cc/NoOperationCC"; import { MessagePriority } from "@zwave-js/core"; import { getDefaultPriority, Message } from "@zwave-js/serial"; import type { ZWaveNode } from "../node/Node"; import { NodeStatus } from "../node/_Types"; import { GetControllerVersionRequest } from "../serialapi/capability/GetControllerVersionMessages"; import { RemoveFailedNodeRequest } from "../serialapi/network-mgmt/RemoveFailedNodeMessages"; import { SendDataRequest } from "../serialapi/transport/SendDataMessages"; import type { Driver } from "./Driver"; import { MessageGenerator, Transaction, TransactionOptions, } from "./Transaction"; function createDummyMessageGenerator(msg: Message): MessageGenerator { return { start: async function* () { this.current = msg; yield msg; }, self: undefined, current: undefined, parent: undefined as any, }; } function createDummyTransaction( driver: any, options: Partial<TransactionOptions>, ): Transaction { options.priority ??= MessagePriority.Normal; options.message ??= {} as any; options.parts = createDummyMessageGenerator(options.message!); return new Transaction(driver, options as TransactionOptions); } interface MockNode { id: number; canSleep: boolean; status: NodeStatus; isCCSecure: ZWaveNode["isCCSecure"]; getEndpoint: ZWaveNode["getEndpoint"]; } describe("lib/driver/Transaction => ", () => { it("should compare priority, then the timestamp", () => { const driverMock = { controller: { nodes: new Map<number, MockNode>(), }, get nodes() { return driverMock.controller.nodes; }, getSafeCCVersionForNode() {}, isCCSecure: () => false, options: { attempts: {}, }, }; // "winning" means the position of a transaction in the queue is lower // t2 has a later timestamp by default const t1 = createDummyTransaction(driverMock, { priority: MessagePriority.Controller, }); const t2 = createDummyTransaction(driverMock, { priority: MessagePriority.Controller, }); // equal priority, earlier timestamp wins expect(t1.compareTo(t2)).toBe(-1); expect(t2.compareTo(t1)).toBe(1); const t3 = createDummyTransaction(driverMock, { priority: MessagePriority.Poll, }); const t4 = createDummyTransaction(driverMock, { priority: MessagePriority.Controller, }); // lower priority loses expect(t3.compareTo(t4)).toBe(1); expect(t4.compareTo(t3)).toBe(-1); // this should not happen but we still need to test it const t5 = createDummyTransaction(driverMock, { priority: MessagePriority.Controller, }); const t6 = createDummyTransaction(driverMock, { priority: MessagePriority.Controller, }); t6.creationTimestamp = t5.creationTimestamp; expect(t5.compareTo(t6)).toBe(0); expect(t6.compareTo(t5)).toBe(0); }); it("NodeQuery comparisons should prioritize listening nodes", () => { interface MockNode { isListening: boolean; isFrequentListening: boolean; isCCSecure: ZWaveNode["isCCSecure"]; getEndpoint: ZWaveNode["getEndpoint"]; } const driverMock = { controller: { nodes: new Map<number, MockNode>([ // 1: non-listening [ 1, { isListening: false, isFrequentListening: false, isCCSecure: () => false, getEndpoint: () => undefined as any, }, ], // 2: listening, but not frequent [ 2, { isListening: true, isFrequentListening: false, isCCSecure: () => false, getEndpoint: () => undefined as any, }, ], // 3: listening, and frequent [ 3, { isListening: true, isFrequentListening: true, isCCSecure: () => false, getEndpoint: () => undefined as any, }, ], // 4: not listening, but frequent listening [ 4, { isListening: false, isFrequentListening: true, isCCSecure: () => false, getEndpoint: () => undefined as any, }, ], ]), }, get nodes() { return driverMock.controller.nodes; }, getSafeCCVersionForNode() {}, isCCSecure: () => false, options: { attempts: {}, }, }; function createTransactionForNode( nodeId: number | undefined, priority: MessagePriority = MessagePriority.NodeQuery, ) { const driver = driverMock as any as Driver; const msg = nodeId != undefined ? new SendDataRequest(driver, { command: new NoOperationCC(driver, { nodeId, }), }) : new GetControllerVersionRequest(driver); const ret = createDummyTransaction(driverMock, { priority, message: msg, }); ret.creationTimestamp = 0; return ret; } const tNone = createTransactionForNode(1); const tList = createTransactionForNode(2); const tFreq = createTransactionForNode(3); const tListFreq = createTransactionForNode(4); const tNoId = createTransactionForNode(undefined as any); const tNoNode = createTransactionForNode(5); const tListNormalPrio = createTransactionForNode( 2, MessagePriority.Normal, ); const tListLowPrio = createTransactionForNode(2, MessagePriority.Poll); const tNoneNormalPrio = createTransactionForNode( 1, MessagePriority.Normal, ); const tNoneLowPrio = createTransactionForNode(1, MessagePriority.Poll); // t2/3/4 prioritized because it's listening and t1 is not expect(tNone.compareTo(tList)).toBe(1); expect(tNone.compareTo(tList)).toBe(1); expect(tNone.compareTo(tFreq)).toBe(1); // sanity checks expect(tList.compareTo(tNone)).toBe(-1); expect(tFreq.compareTo(tNone)).toBe(-1); expect(tListFreq.compareTo(tNone)).toBe(-1); // equal priority because both are (frequent or not) listening expect(tList.compareTo(tFreq)).toBe(0); expect(tList.compareTo(tListFreq)).toBe(0); // sanity checks expect(tFreq.compareTo(tListFreq)).toBe(0); expect(tFreq.compareTo(tList)).toBe(0); expect(tListFreq.compareTo(tList)).toBe(0); // NodeQuery (non listening) should be lower than other priorities (listening) expect(tNone.compareTo(tListLowPrio)).toBe(1); expect(tNone.compareTo(tListNormalPrio)).toBe(1); // sanity checks expect(tListLowPrio.compareTo(tNone)).toBe(-1); expect(tListNormalPrio.compareTo(tNone)).toBe(-1); // The default order should apply when both nodes are not listening expect(tNone.compareTo(tNoneNormalPrio)).toBe(1); expect(tNone.compareTo(tNoneLowPrio)).toBe(-1); // sanity checks expect(tNoneNormalPrio.compareTo(tNone)).toBe(-1); expect(tNoneLowPrio.compareTo(tNone)).toBe(1); // fallbacks for undefined nodes expect(tNoId.compareTo(tNone)).toBe(0); expect(tNoId.compareTo(tList)).toBe(0); expect(tFreq.compareTo(tNoId)).toBe(0); expect(tListFreq.compareTo(tNoId)).toBe(0); expect(tNoNode.compareTo(tNone)).toBe(0); expect(tNoNode.compareTo(tList)).toBe(0); expect(tFreq.compareTo(tNoNode)).toBe(0); expect(tListFreq.compareTo(tNoNode)).toBe(0); }); it("Messages in the wakeup queue should be preferred over lesser priorities only if the node is awake", () => { const driverMock = { controller: { nodes: new Map<number, MockNode>([ // 1: awake [ 1, { id: 1, // non-sleeping canSleep: false, status: NodeStatus.Alive, isCCSecure: () => false, getEndpoint: () => undefined as any, }, ], // 2: not awake [ 2, { id: 2, // sleeping canSleep: true, status: NodeStatus.Asleep, isCCSecure: () => false, getEndpoint: () => undefined as any, }, ], ]), }, get nodes() { return driverMock.controller.nodes; }, getSafeCCVersionForNode() {}, isCCSecure: () => false, options: { attempts: {}, }, } as unknown as Driver; function createTransaction(nodeId: number, priority: MessagePriority) { const driver = driverMock as any as Driver; const msg = new SendDataRequest(driver, { command: new NoOperationCC(driver, { nodeId }), }); const ret = createDummyTransaction(driverMock, { priority, message: msg, }); ret.creationTimestamp = 0; return ret; } // Node awake, higher priority than WakeUp const tAwakeHigh = createTransaction(1, MessagePriority.Controller); // Node awake, WakeUp priority const tAwakeWU = createTransaction(1, MessagePriority.WakeUp); // Node awake, lowest priority const tAwakeLow = createTransaction(1, MessagePriority.Poll); // Node asleep, higher priority than WakeUp const tAsleepHigh = createTransaction(2, MessagePriority.Controller); // Node asleep, WakeUp priority const tAsleepWU = createTransaction(2, MessagePriority.WakeUp); // Node asleep, lowest priority const tAsleepLow = createTransaction(2, MessagePriority.Poll); // For alive nodes, the conventional order should apply expect(tAwakeHigh.compareTo(tAwakeWU)).toBe(-1); expect(tAwakeHigh.compareTo(tAwakeLow)).toBe(-1); expect(tAwakeWU.compareTo(tAwakeLow)).toBe(-1); // Test the opposite direction aswell expect(tAwakeWU.compareTo(tAwakeHigh)).toBe(1); expect(tAwakeLow.compareTo(tAwakeHigh)).toBe(1); expect(tAwakeLow.compareTo(tAwakeWU)).toBe(1); // For asleep nodes, the conventional order should apply too expect(tAsleepHigh.compareTo(tAsleepWU)).toBe(-1); expect(tAsleepHigh.compareTo(tAsleepLow)).toBe(-1); expect(tAsleepWU.compareTo(tAsleepLow)).toBe(-1); // Test the opposite direction aswell expect(tAsleepWU.compareTo(tAsleepHigh)).toBe(1); expect(tAsleepLow.compareTo(tAsleepHigh)).toBe(1); expect(tAsleepLow.compareTo(tAsleepWU)).toBe(1); // The wake-up priority of sleeping nodes is lower than everything else of awake nodes expect(tAsleepWU.compareTo(tAwakeHigh)).toBe(1); expect(tAsleepWU.compareTo(tAwakeWU)).toBe(1); expect(tAsleepWU.compareTo(tAwakeLow)).toBe(1); // Test the opposite direction aswell expect(tAwakeHigh.compareTo(tAsleepWU)).toBe(-1); expect(tAwakeWU.compareTo(tAsleepWU)).toBe(-1); expect(tAwakeLow.compareTo(tAsleepWU)).toBe(-1); }); // Repro for #550 it("Controller message should be preferred over messages for sleeping nodes", () => { const driverMock = { controller: { nodes: new Map<number, MockNode>([ // 1: non-sleeping [ 1, { id: 1, // non-sleeping canSleep: false, status: NodeStatus.Alive, isCCSecure: () => false, getEndpoint: () => undefined as any, }, ], // 2: not awake [ 2, { id: 2, // sleeping canSleep: true, status: NodeStatus.Asleep, isCCSecure: () => false, getEndpoint: () => undefined as any, }, ], ]), }, get nodes() { return driverMock.controller.nodes; }, getSafeCCVersionForNode() {}, isCCSecure: () => false, options: { attempts: {}, }, } as unknown as Driver; let creationTimestamp = 0; function createTransaction( msg: Message, priority: MessagePriority = getDefaultPriority(msg)!, ) { const ret = createDummyTransaction(driverMock, { priority, message: msg, }); ret.creationTimestamp = ++creationTimestamp; return ret; } const msgForSleepingNode = new SendDataRequest(driverMock, { command: new NoOperationCC(driverMock, { nodeId: 2 }), }); const tSleepingNode = createTransaction( msgForSleepingNode, MessagePriority.WakeUp, ); const msgForController = new RemoveFailedNodeRequest(driverMock, { failedNodeId: 3, }); const tController = createTransaction(msgForController); // The controller transaction should have a higher priority expect(tController.compareTo(tSleepingNode)).toBe(-1); }); it("should capture a stack trace where it was created", () => { const driverMock = { controller: { nodes: new Map<number, MockNode>(), }, get nodes() { return driverMock.controller.nodes; }, getSafeCCVersionForNode() {}, isCCSecure: () => false, options: { attempts: {}, }, }; const test = createDummyTransaction(driverMock, { message: "FOOBAR" as any, }); expect(test.stack).toInclude("Transaction.test.ts"); expect(test.stack).not.toInclude("FOOBAR"); }); });