inventoresed
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
226 lines (198 loc) • 6.89 kB
text/typescript
import "@zwave-js/cc";
import { BatteryCCAPI } from "@zwave-js/cc/BatteryCC";
import { VersionCCAPI } from "@zwave-js/cc/VersionCC";
import {
assertZWaveError,
CommandClasses,
ZWaveErrorCodes,
} from "@zwave-js/core";
import type { ThrowingMap } from "@zwave-js/shared";
import { MockController } from "@zwave-js/testing";
import { createDefaultMockControllerBehaviors } from "../../Utils";
import type { Driver } from "../driver/Driver";
import { createAndStartTestingDriver } from "../driver/DriverMock";
import { DeviceClass } from "./DeviceClass";
import { Endpoint } from "./Endpoint";
import { ZWaveNode } from "./Node";
describe("lib/node/Endpoint", () => {
let driver: Driver;
let controller: MockController;
beforeAll(
async () => {
({ driver } = await createAndStartTestingDriver({
skipNodeInterview: true,
loadConfiguration: false,
beforeStartup(mockPort) {
controller = new MockController({ serial: mockPort });
controller.defineBehavior(
...createDefaultMockControllerBehaviors(),
);
},
}));
await driver.configManager.loadDeviceClasses();
},
// Loading configuration may take a while on CI
30000,
);
afterAll(async () => {
await driver.destroy();
});
afterEach(async () => {
driver.networkCache.clear();
driver.valueDB?.clear();
});
describe("createAPI", () => {
it("throws if a non-implemented API should be created", () => {
const endpoint = new Endpoint(1, driver, 1);
assertZWaveError(() => endpoint.createAPI(0xbada55), {
errorCode: ZWaveErrorCodes.CC_NoAPI,
messageMatches: "no associated API",
});
});
it("the returned API throws when trying to access a non-supported CC", async () => {
const endpoint = new Endpoint(1, driver, 1);
// We must not use Basic CC here, because that is assumed to be always supported
const api = endpoint.createAPI(CommandClasses["Binary Sensor"]);
// this does not throw
api.isSupported();
// this does
await assertZWaveError(() => api.get(), {
errorCode: ZWaveErrorCodes.CC_NotSupported,
messageMatches: "Node 1 (endpoint 1) does not support",
});
// It only includes the endpoint number for non-root endpoints
(endpoint as any).index = 0;
await assertZWaveError(() => api.get(), {
errorCode: ZWaveErrorCodes.CC_NotSupported,
messageMatches: "Node 1 does not support",
});
});
});
describe("commandClasses dictionary", () => {
it("throws when trying to access a non-implemented CC", () => {
const endpoint = new Endpoint(1, driver, 1);
assertZWaveError(() => (endpoint.commandClasses as any).FOOBAR, {
errorCode: ZWaveErrorCodes.CC_NotImplemented,
messageMatches: "FOOBAR is not implemented",
});
});
it("throws when trying to use a command of an unsupported CC", () => {
const endpoint = new Endpoint(1, driver, 1);
assertZWaveError(() => endpoint.commandClasses.Battery.get(), {
errorCode: ZWaveErrorCodes.CC_NotSupported,
messageMatches: "does not support the Command Class Battery",
});
});
it("does not throw when checking support of a CC", () => {
const endpoint = new Endpoint(1, driver, 1);
expect(endpoint.commandClasses.Battery.isSupported()).toBeFalse();
});
it("does not throw when accessing the ID of a CC", () => {
const endpoint = new Endpoint(1, driver, 1);
expect(endpoint.commandClasses.Battery.ccId).toBe(
CommandClasses.Battery,
);
});
it("does not throw when scoping the API options", () => {
const endpoint = new Endpoint(1, driver, 1);
endpoint.commandClasses.Battery.withOptions({});
});
it("returns all supported CCs when being enumerated", () => {
// No supported CCs, empty array
let node = new ZWaveNode(2, driver, undefined, []);
let actual = [...node.commandClasses];
expect(actual).toEqual([]);
// Supported and controlled CCs
node = new ZWaveNode(
2,
driver,
undefined,
[CommandClasses.Battery, CommandClasses.Version],
[CommandClasses["Wake Up"]],
);
actual = [...node.commandClasses];
expect(actual).toHaveLength(2);
expect(actual.map((api) => api.constructor)).toIncludeAllMembers([
BatteryCCAPI,
VersionCCAPI,
// WakeUpCCAPI is not supported (only controlled), so no API!
]);
node.destroy();
});
it("returns [object Object] when turned into a string", () => {
const node = new ZWaveNode(2, driver, undefined, []);
expect((node.commandClasses as any)[Symbol.toStringTag]).toBe(
"[object Object]",
);
node.destroy();
});
it("returns undefined for other symbol properties", () => {
const node = new ZWaveNode(2, driver, undefined, []);
expect(
(node.commandClasses as any)[Symbol.unscopables],
).toBeUndefined();
node.destroy();
});
});
describe("createCCInstance()", () => {
it("returns undefined if the node supports the CC but it is not yet implemented", () => {
const endpoint = new Endpoint(1, driver, 1);
const cc = 0xbada55;
endpoint.addCC(cc, { isSupported: true });
const instance = endpoint.createCCInstance(cc);
expect(instance).toBeUndefined();
});
});
describe("Device Class quirks", () => {
it("A non-root endpoint with the `Power Strip Switch` device class does not support the Multi Channel CC", async () => {
const powerStripSwitch = new DeviceClass(
driver.configManager,
0x01,
0x10,
0x04,
);
const node = new ZWaveNode(1, driver, powerStripSwitch);
expect(node.supportsCC(CommandClasses["Multi Channel"])).toBeTrue();
const ep = new Endpoint(1, driver, 1, powerStripSwitch);
expect(ep.supportsCC(CommandClasses["Multi Channel"])).toBeFalse();
});
it("Non-root endpoints should not have the Manufacturer Specific CC (among others) added as mandatory", async () => {
const soundSwitch = new DeviceClass(
driver.configManager,
0x01,
0x03,
0x01,
);
const node = new ZWaveNode(1, driver, soundSwitch);
(driver.controller.nodes as ThrowingMap<number, ZWaveNode>).set(
1,
node,
);
expect(
node.supportsCC(CommandClasses["Manufacturer Specific"]),
).toBeTrue();
const ep = new Endpoint(1, driver, 1, soundSwitch);
expect(
ep.supportsCC(CommandClasses["Manufacturer Specific"]),
).toBeFalse();
});
it("Always-listening nodes should not have the Battery CC added as mandatory", async () => {
const soundSwitch = new DeviceClass(
driver.configManager,
0x01,
0x03,
0x01,
);
const node = new ZWaveNode(1, driver);
(driver.controller.nodes as ThrowingMap<number, ZWaveNode>).set(
1,
node,
);
node["isListening"] = true;
node["applyDeviceClass"](soundSwitch);
expect(node.supportsCC(CommandClasses.Battery)).toBeFalse();
const ep = new Endpoint(1, driver, 1, soundSwitch);
expect(ep.supportsCC(CommandClasses.Battery)).toBeFalse();
});
});
});