UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

353 lines (321 loc) 11 kB
import { assertZWaveError, ZWaveErrorCodes } from "@zwave-js/core"; import { createTestingHost, TestingHost } from "@zwave-js/host"; import { FunctionType, MessageType } from "./Constants"; import type { INodeQuery } from "./INodeQuery"; import { Message, messageTypes } from "./Message"; describe("lib/message", () => { let host: TestingHost; beforeEach(() => { host = createTestingHost(); }); describe("Message", () => { it("should deserialize and serialize correctly", () => { // actual messages from OZW const okayMessages = [ Buffer.from([ 0x01, 0x09, 0x00, 0x13, 0x03, 0x02, 0x00, 0x00, 0x25, 0x0b, 0xca, ]), Buffer.from([0x01, 0x05, 0x00, 0x47, 0x04, 0x20, 0x99]), Buffer.from([0x01, 0x06, 0x00, 0x46, 0x0c, 0x0d, 0x32, 0x8c]), Buffer.from([ 0x01, 0x0a, 0x00, 0x13, 0x03, 0x03, 0x8e, 0x02, 0x04, 0x25, 0x40, 0x0b, ]), ]; for (const original of okayMessages) { const parsed = new Message(host, { data: original }); expect(parsed.serialize()).toEqual(original); } }); it("should serialize correctly when the payload is null", () => { // synthetic message const expected = Buffer.from([0x01, 0x03, 0x00, 0xff, 0x03]); const message = new Message(host, { type: MessageType.Request, functionType: 0xff, }); expect(message.serialize()).toEqual(expected); }); it("should throw the correct error when parsing a faulty message", () => { // fake messages to produce certain errors const brokenMessages: [Buffer, string, ZWaveErrorCodes][] = [ // too short (<5 bytes) [ Buffer.from([0x01, 0x02, 0x00, 0x00]), "truncated", ZWaveErrorCodes.PacketFormat_Truncated, ], // no SOF [ Buffer.from([0x00, 0x03, 0x00, 0x00, 0x00]), "start with SOF", ZWaveErrorCodes.PacketFormat_Invalid, ], // too short for the provided data length [ Buffer.from([0x01, 0x04, 0x00, 0x00, 0x00]), "truncated", ZWaveErrorCodes.PacketFormat_Truncated, ], // invalid checksum [ Buffer.from([0x01, 0x03, 0x00, 0x00, 0x00]), "checksum", ZWaveErrorCodes.PacketFormat_Checksum, ], // invalid checksum (once more with a real packet) [ Buffer.from([0x01, 0x05, 0x00, 0x47, 0x04, 0x20, 0x98]), "checksum", ZWaveErrorCodes.PacketFormat_Checksum, ], ]; for (const [message, msg, code] of brokenMessages) { assertZWaveError(() => new Message(host, { data: message }), { messageMatches: msg, errorCode: code, }); } }); it("isComplete() should work correctly", () => { // actual messages from OZW const okayMessages = [ Buffer.from([ 0x01, 0x09, 0x00, 0x13, 0x03, 0x02, 0x00, 0x00, 0x25, 0x0b, 0xca, ]), Buffer.from([0x01, 0x05, 0x00, 0x47, 0x04, 0x20, 0x99]), Buffer.from([0x01, 0x06, 0x00, 0x46, 0x0c, 0x0d, 0x32, 0x8c]), Buffer.from([ 0x01, 0x0a, 0x00, 0x13, 0x03, 0x03, 0x8e, 0x02, 0x04, 0x25, 0x40, 0x0b, ]), ]; for (const msg of okayMessages) { expect(Message.isComplete(msg)).toBe(true); // `${msg.toString("hex")} should be detected as complete` } // truncated messages const truncatedMessages = [ undefined, Buffer.from([]), Buffer.from([0x01]), Buffer.from([0x01, 0x09]), Buffer.from([0x01, 0x09, 0x00]), Buffer.from([ 0x01, 0x09, 0x00, 0x13, 0x03, 0x02, 0x00, 0x00, 0x25, 0x0b, ]), ]; for (const msg of truncatedMessages) { expect(Message.isComplete(msg)).toBe(false); // `${msg ? msg.toString("hex") : "null"} should be detected as incomplete` } // faulty but non-truncated messages should be detected as complete const faultyMessages = [ Buffer.from([ 0x01, 0x09, 0x00, 0x13, 0x03, 0x02, 0x00, 0x00, 0x25, 0x0b, 0xca, ]), Buffer.from([0x01, 0x05, 0x00, 0x47, 0x04, 0x20, 0x99]), Buffer.from([0x01, 0x06, 0x00, 0x46, 0x0c, 0x0d, 0x32, 0x8c]), Buffer.from([ 0x01, 0x0a, 0x00, 0x13, 0x03, 0x03, 0x8e, 0x02, 0x04, 0x25, 0x40, 0x0b, ]), ]; for (const msg of faultyMessages) { expect(Message.isComplete(msg)).toBe(true); // `${msg.toString("hex")} should be detected as complete` } // actual messages from OZW, appended with some random data const tooLongMessages = [ Buffer.from([ 0x01, 0x09, 0x00, 0x13, 0x03, 0x02, 0x00, 0x00, 0x25, 0x0b, 0xca, 0x00, ]), Buffer.from([ 0x01, 0x05, 0x00, 0x47, 0x04, 0x20, 0x99, 0x01, 0x02, ]), Buffer.from([ 0x01, 0x06, 0x00, 0x46, 0x0c, 0x0d, 0x32, 0x8c, 0xab, 0xcd, 0xef, ]), Buffer.from([ 0x01, 0x0a, 0x00, 0x13, 0x03, 0x03, 0x8e, 0x02, 0x04, 0x25, 0x40, 0x0b, 0x12, ]), ]; for (const msg of tooLongMessages) { expect(Message.isComplete(msg)).toBe(true); // `${msg.toString("hex")} should be detected as complete` } }); it("toJSON() should return a semi-readable JSON representation", () => { const msg1 = new Message(host, { type: MessageType.Request, functionType: FunctionType.GetControllerVersion, }); const json1 = { name: msg1.constructor.name, type: "Request", functionType: "GetControllerVersion", payload: "", }; const msg2 = new Message(host, { type: MessageType.Request, functionType: FunctionType.GetControllerVersion, payload: Buffer.from("aabbcc", "hex"), }); const json2 = { name: msg2.constructor.name, type: "Request", functionType: "GetControllerVersion", payload: "aabbcc", }; const msg3 = new Message(host, { type: MessageType.Response, functionType: FunctionType.GetControllerVersion, expectedResponse: FunctionType.GetControllerVersion, }); const json3 = { name: msg3.constructor.name, type: "Response", functionType: "GetControllerVersion", expectedResponse: "GetControllerVersion", payload: "", }; const msg4 = new Message(host, { type: MessageType.Request, functionType: FunctionType.GetControllerVersion, expectedResponse: FunctionType.GetControllerVersion, payload: Buffer.from("aabbcc", "hex"), }); const json4 = { name: msg4.constructor.name, type: "Request", functionType: "GetControllerVersion", expectedResponse: "GetControllerVersion", payload: "aabbcc", }; expect(msg1.toJSON()).toEqual(json1); expect(msg2.toJSON()).toEqual(json2); expect(msg3.toJSON()).toEqual(json3); expect(msg4.toJSON()).toEqual(json4); }); it("getConstructor() should return `Message` for an unknown packet type", () => { const unknown = Buffer.from([0x01, 0x03, 0x00, 0x00, 0xfc]); expect(Message.getConstructor(unknown)).toBe(Message); }); // it(`when the expectedResponse is defined, testResponse() should return "final" or "unexpected"`, () => { // const msg = new Message(fakeDriver, { // type: MessageType.Request, // functionType: 0xff, // expectedResponse: FunctionType.ApplicationCommand, // }); // const final = new Message(fakeDriver, { // type: MessageType.Response, // functionType: FunctionType.ApplicationCommand, // }); // expect(msg.testResponse(final)).toBe("final"); // // wrong function type // const unexpected1 = new Message(fakeDriver, { // type: MessageType.Response, // functionType: FunctionType.SendData, // }); // expect(msg.testResponse(unexpected1)).toBe("unexpected"); // // not a response // const unexpected2 = new Message(fakeDriver, { // type: MessageType.Request, // functionType: 0xff, // }); // expect(msg.testResponse(unexpected2)).toBe("unexpected"); // }); // it(`when the message has a callbackId, testResponse() should return "unexpected" for requests that don't match it`, () => { // const msg = new Message(fakeDriver, { // type: MessageType.Request, // functionType: 0xff, // expectedCallback: FunctionType.GetSUCNodeId, // callbackId: 5, // }); // const final = new Message(fakeDriver, { // type: MessageType.Request, // functionType: FunctionType.GetSUCNodeId, // callbackId: 5, // }); // expect(msg.testResponse(final)).toBe("final"); // // wrong callback id // const unexpected1 = new Message(fakeDriver, { // type: MessageType.Request, // functionType: FunctionType.GetSUCNodeId, // callbackId: 4, // }); // expect(msg.testResponse(unexpected1)).toBe("unexpected"); // // missing callback id // const unexpected2 = new Message(fakeDriver, { // type: MessageType.Request, // functionType: FunctionType.GetSUCNodeId, // }); // expect(msg.testResponse(unexpected2)).toBe("unexpected"); // // sanity check: the function type should still be checked // const unexpected3 = new Message(fakeDriver, { // type: MessageType.Request, // functionType: FunctionType.RequestNodeInfo, // does not match // callbackId: 5, // matches // }); // expect(msg.testResponse(unexpected3)).toBe("unexpected"); // }); it(`the constructor should throw when no message type is specified`, () => { assertZWaveError(() => new Message(host, { functionType: 0xff }), { errorCode: ZWaveErrorCodes.Argument_Invalid, messageMatches: /message type/i, }); @messageTypes(undefined as any, 0xff) class FakeMessageWithoutMessageType extends Message {} assertZWaveError(() => new FakeMessageWithoutMessageType(host), { errorCode: ZWaveErrorCodes.Argument_Invalid, messageMatches: /message type/i, }); }); it(`the constructor should throw when no function type is specified`, () => { assertZWaveError( () => new Message(host, { type: MessageType.Request }), { errorCode: ZWaveErrorCodes.Argument_Invalid, messageMatches: /function type/i, }, ); @messageTypes(MessageType.Request, undefined as any) class FakeMessageWithoutFunctionType extends Message {} assertZWaveError(() => new FakeMessageWithoutFunctionType(host), { errorCode: ZWaveErrorCodes.Argument_Invalid, messageMatches: /function type/i, }); }); describe("getNodeUnsafe()", () => { it("returns undefined when the controller is not initialized yet", () => { const msg = new Message(host, { type: MessageType.Request, functionType: 0xff, }); expect(msg.getNodeUnsafe(host)).toBeUndefined(); }); it("returns undefined when the message is no node query", () => { const msg = new Message(host, { type: MessageType.Request, functionType: 0xff, }); expect(msg.getNodeUnsafe(host)).toBeUndefined(); }); it("returns the associated node otherwise", () => { host.nodes.set(1, {} as any); const msg = new Message(host, { type: MessageType.Request, functionType: 0xff, }); // This node exists (msg as any as INodeQuery).nodeId = 1; expect(msg.getNodeUnsafe(host)).toBe(host.nodes.get(1)); // This one does (msg as any as INodeQuery).nodeId = 2; expect(msg.getNodeUnsafe(host)).toBeUndefined(); }); }); }); });