zigbee-herdsman
Version:
An open source ZigBee gateway solution with node.js.
280 lines (237 loc) • 11.7 kB
text/typescript
import {describe, expect, it, vi} from "vitest";
import {checkInstallCode} from "../src/controller/helpers/installCodes";
import {Queue, Utils, Waitress, wait} from "../src/utils";
import {logger, setLogger} from "../src/utils/logger";
const mockLogger = {
debug: vi.fn(),
info: vi.fn(),
warning: vi.fn(),
error: vi.fn(),
};
describe("Utils", () => {
it("Is Number Array", () => {
expect(Utils.isNumberArray([1, 2, 3])).toBeTruthy();
expect(Utils.isNumberArray([1, 2, "3"])).toBeFalsy();
expect(Utils.isNumberArray("nonarray")).toBeFalsy();
});
it("Is Number Array of length", () => {
expect(Utils.isNumberArrayOfLength([1, 2, 3], 3)).toBeTruthy();
expect(Utils.isNumberArrayOfLength([1, 2], 3)).toBeFalsy();
expect(Utils.isNumberArrayOfLength([1, 2, "3"], 3)).toBeFalsy();
expect(Utils.isNumberArrayOfLength("nonarray", 3)).toBeFalsy();
});
it("Is object empty", () => {
expect(Utils.isObjectEmpty({})).toBeTruthy();
expect(Utils.isObjectEmpty({a: 1})).toBeFalsy();
});
it("Assert string", () => {
expect(Utils.assertString("bla")).toBeUndefined();
expect(() => {
Utils.assertString(1);
}).toThrow("Input must be a string!");
});
it("Test wait", () => {
const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout").mockImplementationOnce(
// @ts-expect-error mocked
() => {},
);
wait(1000).then(() => {});
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
setTimeoutSpy.mockRestore();
});
it("Test waitress", async () => {
vi.useFakeTimers();
const validator = (payload: string, matcher: number): boolean => {
if (payload === "one" && matcher === 1) return true;
if (payload === "two" && matcher === 2) return true;
return false;
};
const waitress = new Waitress<string, number>(validator, (_, timeout) => `Timedout '${timeout}'`);
const wait1 = waitress.waitFor(1, 10000).start();
waitress.resolve("one");
expect(await wait1.promise).toBe("one");
const wait2_1 = waitress.waitFor(2, 10000).start();
const wait2_2 = waitress.waitFor(2, 10000).start();
const wait2_3 = waitress.waitFor(2, 10000).start();
const wait2_4 = waitress.waitFor(2, 5000).start();
const wait2_5 = waitress.waitFor(2, 5000).start();
waitress.remove(wait2_3.ID);
vi.advanceTimersByTime(6000);
waitress.remove(wait2_5.ID);
waitress.resolve("two");
expect(await wait2_1.promise).toBe("two");
expect(await wait2_2.promise).toBe("two");
let error2;
try {
await wait2_4.promise;
} catch (e) {
error2 = e;
}
expect(error2).toStrictEqual(new Error("Timedout '5000'"));
let error3;
try {
await wait2_5.promise;
} catch (e) {
error3 = e;
}
expect(error3).toStrictEqual(new Error("Timedout '5000'"));
vi.useRealTimers();
// reject test
const wait1b = waitress.waitFor(1, 5000).start();
let error1_;
wait(1000).then(() => {
waitress.reject("one", "drop");
});
try {
await wait1b.promise;
} catch (e) {
error1_ = e;
}
expect(error1_).toStrictEqual(new Error("drop"));
vi.useFakeTimers();
const wait2 = waitress.waitFor(2, 5000).start();
const handled1 = waitress.reject("tree", "drop");
expect(handled1).toBe(false);
let error2_;
vi.advanceTimersByTime(6000);
try {
await wait2.promise;
} catch (e) {
error2_ = e;
}
expect(error2_).toStrictEqual(new Error("Timedout '5000'"));
const handled2 = waitress.reject("two", "drop");
expect(handled2).toBe(false);
waitress.waitFor(2, 10000).start().promise;
waitress.waitFor(2, 10000).start().promise;
await vi.advanceTimersByTimeAsync(2000);
waitress.clear();
await vi.advanceTimersByTimeAsync(12000);
// @ts-expect-error private
expect(waitress.waiters.size).toStrictEqual(0);
vi.useRealTimers();
});
it("Test queue", async () => {
const queue = new Queue(4);
const finished: number[] = [];
let job1Promise: (() => void) | undefined;
let job2Promise: (() => void) | undefined;
const job1 = new Promise<void>((resolve) => {
job1Promise = resolve;
});
const job2 = new Promise<void>((resolve) => {
job2Promise = resolve;
});
const job5 = new Promise((_resolve) => {});
const job6 = new Promise((_resolve) => {});
const job7 = new Promise((_resolve) => {});
const job1Result = queue.execute<string>(async () => {
await job1;
finished.push(1);
return "finished";
});
const job2Result = queue.execute<void>(async () => {
await job2;
finished.push(2);
}, "mykey");
queue.execute<void>(async () => {
finished.push(3);
await Promise.resolve();
}, "mykey");
queue.execute<void>(async () => {
finished.push(4);
await Promise.resolve();
}, "mykey2");
queue.execute<void>(async () => {
await job5;
finished.push(5);
});
queue.execute<void>(async () => {
await job6;
finished.push(6);
});
queue.execute<void>(async () => {
await job7;
finished.push(7);
});
queue.execute<void>(async () => {
finished.push(8);
await Promise.resolve();
});
expect(finished).toEqual([4]);
job1Promise?.();
expect(await job1Result).toBe("finished");
await job1Result;
expect(finished).toEqual([4, 1]);
job2Promise?.();
await job2Result;
expect(finished).toEqual([4, 1, 2, 3]);
expect(queue.count()).toBe(5);
});
it("Logs", () => {
const debugSpy = vi.spyOn(console, "debug");
const infoSpy = vi.spyOn(console, "info");
const warningSpy = vi.spyOn(console, "warn");
const errorSpy = vi.spyOn(console, "error");
logger.debug("debug", "zh");
expect(debugSpy).toHaveBeenCalledWith(expect.stringMatching(/^\[\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\d\d\dZ\] zh: debug$/));
logger.info("info", "zh");
expect(infoSpy).toHaveBeenCalledWith(expect.stringMatching(/^\[\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\d\d\dZ\] zh: info$/));
logger.warning("warning", "zh");
expect(warningSpy).toHaveBeenCalledWith(expect.stringMatching(/^\[\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\d\d\dZ\] zh: warning$/));
logger.error("error", "zh");
expect(errorSpy).toHaveBeenCalledWith(expect.stringMatching(/^\[\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\d\d\dZ\] zh: error$/));
setLogger(mockLogger);
expect(logger).toEqual(mockLogger);
logger.debug("debug", "zh");
expect(mockLogger.debug).toHaveBeenCalledWith("debug", "zh");
logger.info("info", "zh");
expect(mockLogger.info).toHaveBeenCalledWith("info", "zh");
logger.warning("warning", "zh");
expect(mockLogger.warning).toHaveBeenCalledWith("warning", "zh");
logger.error("error", "zh");
expect(mockLogger.error).toHaveBeenCalledWith("error", "zh");
});
it("Checks install codes of all lengths", () => {
expect(() => checkInstallCode(Buffer.from("001122", "hex"))).toThrow("Install code 001122 has invalid size");
const code8Valid = Buffer.from("83FED3407A932B70", "hex");
const code8Invalid = Buffer.from("FFFED3407A939723", "hex");
const code8InvalidFixed = Buffer.from("FFFED3407A93DE84", "hex");
const code8MissingCRC = Buffer.from("83FED3407A93", "hex");
expect(checkInstallCode(code8Valid)).toStrictEqual([code8Valid, undefined]);
expect(checkInstallCode(code8Invalid)).toStrictEqual([code8InvalidFixed, "invalid CRC"]);
expect(() => checkInstallCode(code8Invalid, false)).toThrow(`Install code ${code8Invalid.toString("hex")} failed CRC validation`);
expect(checkInstallCode(code8MissingCRC)).toStrictEqual([code8Valid, "missing CRC"]);
expect(() => checkInstallCode(code8MissingCRC, false)).toThrow(`Install code ${code8MissingCRC.toString("hex")} failed CRC validation`);
const code10Valid = Buffer.from("83FED3407A93972397FC", "hex");
const code10Invalid = Buffer.from("FFFED3407A939723A5C6", "hex");
const code10InvalidFixed = Buffer.from("FFFED3407A9397238C4F", "hex");
// consired as 8-length with invalid CRC
const code10MissingCRC = Buffer.from("83FED3407A939723", "hex");
const code10MissingCRCFixed = Buffer.from("83FED3407A932B70", "hex");
expect(checkInstallCode(code10Valid)).toStrictEqual([code10Valid, undefined]);
expect(checkInstallCode(code10Invalid)).toStrictEqual([code10InvalidFixed, "invalid CRC"]);
expect(() => checkInstallCode(code10Invalid, false)).toThrow(`Install code ${code10Invalid.toString("hex")} failed CRC validation`);
expect(checkInstallCode(code10MissingCRC)).toStrictEqual([code10MissingCRCFixed, "invalid CRC"]);
expect(() => checkInstallCode(code10MissingCRC, false)).toThrow(`Install code ${code10MissingCRC.toString("hex")} failed CRC validation`);
const code14Valid = Buffer.from("83FED3407A939723A5C639FF4C12", "hex");
const code14Invalid = Buffer.from("FFFED3407A939723A5C639FF4C12", "hex");
const code14InvalidFixed = Buffer.from("FFFED3407A939723A5C639FFDE74", "hex");
const code14MissingCRC = Buffer.from("83FED3407A939723A5C639FF", "hex");
expect(checkInstallCode(code14Valid)).toStrictEqual([code14Valid, undefined]);
expect(checkInstallCode(code14Invalid)).toStrictEqual([code14InvalidFixed, "invalid CRC"]);
expect(() => checkInstallCode(code14Invalid, false)).toThrow(`Install code ${code14Invalid.toString("hex")} failed CRC validation`);
expect(checkInstallCode(code14MissingCRC)).toStrictEqual([code14Valid, "missing CRC"]);
expect(() => checkInstallCode(code14MissingCRC, false)).toThrow(`Install code ${code14MissingCRC.toString("hex")} failed CRC validation`);
const code18Valid = Buffer.from("83FED3407A939723A5C639B26916D505C3B5", "hex");
const code18Invalid = Buffer.from("FFFED3407A939723A5C639B26916D505C3B5", "hex");
const code18InvalidFixed = Buffer.from("FFFED3407A939723A5C639B26916D505EEB1", "hex");
const code18MissingCRC = Buffer.from("83FED3407A939723A5C639B26916D505", "hex");
expect(checkInstallCode(code18Valid)).toStrictEqual([code18Valid, undefined]);
expect(checkInstallCode(code18Invalid)).toStrictEqual([code18InvalidFixed, "invalid CRC"]);
expect(() => checkInstallCode(code18Invalid, false)).toThrow(`Install code ${code18Invalid.toString("hex")} failed CRC validation`);
expect(checkInstallCode(code18MissingCRC)).toStrictEqual([code18Valid, "missing CRC"]);
expect(() => checkInstallCode(code18MissingCRC, false)).toThrow(`Install code ${code18MissingCRC.toString("hex")} failed CRC validation`);
});
});