inventoresed
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
395 lines (352 loc) • 10.9 kB
text/typescript
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,
});
});
});
});