UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

395 lines (352 loc) 10.9 kB
import { createDefaultTransportFormat, getDirectionPrefix, MessagePriority, ZWaveLogContainer, } from "@zwave-js/core"; import { FunctionType, Message, MessageType } from "@zwave-js/serial"; import { assertLogInfo, assertMessage, SpyTransport } from "@zwave-js/testing"; import { createDeferredPromise } from "alcalzone-shared/deferred-promise"; import { SortedList } from "alcalzone-shared/sorted-list"; import colors from "ansi-colors"; import MockDate from "mockdate"; import type { Driver } from "../driver/Driver"; import { createAndStartTestingDriver } from "../driver/DriverMock"; import { Transaction } from "../driver/Transaction"; import { DriverLogger } from "./Driver"; interface CreateMessageOptions { type: MessageType; functionType: FunctionType; } interface CreateTransactionOptions extends CreateMessageOptions { priority: MessagePriority; } describe("lib/log/Driver =>", () => { let driverLogger: DriverLogger; let spyTransport: SpyTransport; let driver: Driver; function createMessage( driver: Driver, options: Partial<CreateTransactionOptions>, ) { return new Message(driver, { type: options.type || MessageType.Request, functionType: options.functionType || (0x00 as any), }); } function createTransaction( options: Partial<CreateTransactionOptions>, ): Transaction { const message = createMessage(driver, options); const trns = new Transaction(driver, { message, parts: {} as any, promise: createDeferredPromise(), priority: options.priority || MessagePriority.Controller, }); return trns; } beforeAll(async () => { ({ driver } = await createAndStartTestingDriver({ loadConfiguration: false, skipNodeInterview: true, skipControllerIdentification: true, // beforeStartup(mockPort) { // controller = new MockController({ serial: mockPort }); // controller.defineBehavior( // ...createDefaultMockControllerBehaviors(), // ); // }, })); }, 30000); afterAll(async () => { await driver.destroy(); }); // Replace all defined transports with a spy transport beforeAll(() => { spyTransport = new SpyTransport(); spyTransport.format = createDefaultTransportFormat(true, true); driverLogger = new DriverLogger( driver, new ZWaveLogContainer({ transports: [spyTransport], }), ); // Uncomment this to debug the log outputs manually // wasSilenced = unsilence(driverLogger); MockDate.set(new Date().setHours(0, 0, 0, 0)); }); // Don't spam the console when performing the other tests not related to logging afterAll(() => { driverLogger.container.updateConfiguration({ enabled: false }); MockDate.reset(); }); beforeEach(() => { spyTransport.spy.mockClear(); }); describe("print()", () => { it("logs short messages correctly", () => { driverLogger.print("Test"); assertMessage(spyTransport, { message: ` Test`, }); }); it("logs long messages correctly", () => { driverLogger.print( "This is a very long message that should be broken into multiple lines maybe sometimes...", ); assertMessage(spyTransport, { message: ` This is a very long message that should be broken into multiple lines maybe so metimes...`, }); }); it("logs with the given loglevel", () => { driverLogger.print("Test", "warn"); assertLogInfo(spyTransport, { level: "warn" }); }); it("has a default loglevel of verbose", () => { driverLogger.print("Test"); assertLogInfo(spyTransport, { level: "verbose" }); }); it("prefixes the messages with the current timestamp and channel name", () => { driverLogger.print("Whatever"); assertMessage(spyTransport, { message: `00:00:00.000 DRIVER Whatever`, ignoreTimestamp: false, ignoreChannel: false, }); }); it("the timestamp is in a dim color", () => { driverLogger.print("Whatever"); assertMessage(spyTransport, { predicate: (msg) => msg.startsWith(colors.gray("00:00:00.000")), ignoreTimestamp: false, ignoreChannel: false, ignoreColor: false, }); }); it("the channel name is in inverted gray color", () => { driverLogger.print("Whatever"); assertMessage(spyTransport, { predicate: (msg) => msg.startsWith(colors.gray.inverse("DRIVER")), ignoreChannel: false, ignoreColor: false, }); }); }); describe("transaction() (for outbound messages)", () => { it("contains the direction", () => { driverLogger.transaction(createTransaction({})); assertMessage(spyTransport, { predicate: (msg) => msg.startsWith(getDirectionPrefix("outbound")), }); }); it("contains the message type as a tag", () => { driverLogger.transaction( createTransaction({ type: MessageType.Request }), ); assertMessage(spyTransport, { predicate: (msg) => msg.includes("[REQ]"), }); driverLogger.transaction( createTransaction({ type: MessageType.Response }), ); assertMessage(spyTransport, { predicate: (msg) => msg.includes("[RES]"), callNumber: 1, }); }); it("contains the function type as a tag", () => { driverLogger.transaction( createTransaction({ functionType: FunctionType.GetSerialApiInitData, }), ); assertMessage(spyTransport, { predicate: (msg) => msg.includes("[GetSerialApiInitData]"), }); }); it("contains the message priority", () => { driverLogger.transaction( createTransaction({ priority: MessagePriority.MultistepController, }), ); assertMessage(spyTransport, { predicate: (msg) => msg.includes("[P: MultistepController]"), }); }); // it("contains no message priority on further attempts", () => { // const transaction = createTransaction({ // priority: MessagePriority.MultistepController, // }); // transaction.sendAttempts = 2; // driverLogger.transaction(transaction); // assertMessage(spyTransport, { // predicate: (msg) => !msg.includes("[P: MultistepController]"), // }); // }); // it("contains the number of send attempts after the first try", () => { // const transaction = createTransaction({ // priority: MessagePriority.MultistepController, // }); // transaction.sendAttempts = 2; // transaction.maxSendAttempts = 3; // driverLogger.transaction(transaction); // assertMessage(spyTransport, { // predicate: (msg) => msg.includes("[attempt 2/3]"), // }); // }); }); describe("transactionResponse() (for inbound messages)", () => { it("contains the direction", () => { const msg = createMessage(driver, {}); driverLogger.transactionResponse(msg, undefined, null as any); assertMessage(spyTransport, { predicate: (msg) => msg.startsWith(getDirectionPrefix("inbound")), }); }); it("contains the message type as a tag", () => { let msg = createMessage(driver, { type: MessageType.Request, }); driverLogger.transactionResponse(msg, undefined, null as any); assertMessage(spyTransport, { predicate: (msg) => msg.includes("[REQ]"), }); msg = createMessage(driver, { type: MessageType.Response, }); driverLogger.transactionResponse(msg, undefined, null as any); assertMessage(spyTransport, { predicate: (msg) => msg.includes("[RES]"), callNumber: 1, }); }); it("contains the function type as a tag", () => { const msg = createMessage(driver, { functionType: FunctionType.HardReset, }); driverLogger.transactionResponse(msg, undefined, null as any); assertMessage(spyTransport, { predicate: (msg) => msg.includes("[HardReset]"), }); }); it("contains the role (regarding the transaction) of the received message as a tag", () => { const msg = createMessage(driver, { functionType: FunctionType.HardReset, }); driverLogger.transactionResponse( msg, undefined, "fatal_controller", ); assertMessage(spyTransport, { predicate: (msg) => msg.includes("[fatal_controller]"), }); }); }); describe("sendQueue()", () => { it("prints the send queue length", () => { const queue = new SortedList<Transaction>(); driverLogger.sendQueue(queue); assertMessage(spyTransport, { predicate: (msg) => msg.includes("(0 messages)"), }); queue.add( createTransaction({ functionType: FunctionType.GetSUCNodeId }), ); driverLogger.sendQueue(queue); assertMessage(spyTransport, { predicate: (msg) => msg.includes("(1 message)"), callNumber: 1, }); queue.add( createTransaction({ functionType: FunctionType.GetSUCNodeId }), ); driverLogger.sendQueue(queue); assertMessage(spyTransport, { predicate: (msg) => msg.includes("(2 messages)"), callNumber: 2, }); }); it("prints the function type for each message", () => { const queue = new SortedList<Transaction>(); queue.add( createTransaction({ functionType: FunctionType.GetSUCNodeId }), ); queue.add( createTransaction({ functionType: FunctionType.HardReset }), ); driverLogger.sendQueue(queue); assertMessage(spyTransport, { predicate: (msg) => msg.includes("GetSUCNodeId"), }); assertMessage(spyTransport, { predicate: (msg) => msg.includes("HardReset"), }); }); it("prints the message type for each message", () => { const queue = new SortedList<Transaction>(); queue.add( createTransaction({ functionType: FunctionType.GetSUCNodeId, type: MessageType.Request, }), ); queue.add( createTransaction({ functionType: FunctionType.HardReset, type: MessageType.Response, }), ); driverLogger.sendQueue(queue); assertMessage(spyTransport, { predicate: (msg) => msg.includes("· [REQ] GetSUCNodeId"), }); assertMessage(spyTransport, { predicate: (msg) => msg.includes("· [RES] HardReset"), }); }); }); describe("colors", () => { it("primary tags are printed in inverse colors", () => { const msg = createMessage(driver, { functionType: FunctionType.HardReset, type: MessageType.Response, }); driverLogger.transactionResponse(msg, undefined, null as any); const expected1 = colors.cyan( colors.bgCyan("[") + colors.inverse("RES") + colors.bgCyan("]") + " " + colors.bgCyan("[") + colors.inverse("HardReset") + colors.bgCyan("]"), ); assertMessage(spyTransport, { predicate: (msg) => msg.includes(expected1), ignoreColor: false, }); }); it("inline tags are printed in inverse colors", () => { driverLogger.print(`This is a message [with] [inline] tags...`); const expected1 = colors.bgCyan("[") + colors.inverse("with") + colors.bgCyan("]") + " " + colors.bgCyan("[") + colors.inverse("inline") + colors.bgCyan("]"); assertMessage(spyTransport, { predicate: (msg) => msg.includes(expected1), ignoreColor: false, }); }); }); });