UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

224 lines (199 loc) 6.13 kB
/* eslint-disable @typescript-eslint/no-empty-function */ import type { MockPortBinding } from "@zwave-js/serial/mock"; import { MockController, MockNode, MockNodeOptions } from "@zwave-js/testing"; import { wait } from "alcalzone-shared/async"; import crypto from "crypto"; import fs from "fs-extra"; import os from "os"; import path from "path"; import { createDefaultMockControllerBehaviors, createDefaultMockNodeBehaviors, } from "../../Utils"; import type { Driver } from "../driver/Driver"; import { createAndStartDriverWithMockPort, CreateAndStartDriverWithMockPortResult, } from "../driver/DriverMock"; import type { ZWaveOptions } from "../driver/ZWaveOptions"; import type { ZWaveNode } from "../node/Node"; function prepareDriver( cacheDir: string = path.join(__dirname, "cache"), logToFile: boolean = false, additionalOptions: Partial<ZWaveOptions> = {}, ): Promise<CreateAndStartDriverWithMockPortResult> { return createAndStartDriverWithMockPort({ ...additionalOptions, portAddress: "/tty/FAKE", ...(logToFile ? { logConfig: { filename: path.join( cacheDir, "logs", "zwavejs_%DATE%.log", ), logToFile: true, enabled: true, level: "debug", }, } : {}), securityKeys: { S0_Legacy: Buffer.from("0102030405060708090a0b0c0d0e0f10", "hex"), S2_Unauthenticated: Buffer.from( "11111111111111111111111111111111", "hex", ), S2_Authenticated: Buffer.from( "22222222222222222222222222222222", "hex", ), S2_AccessControl: Buffer.from( "33333333333333333333333333333333", "hex", ), }, storage: { cacheDir: cacheDir, lockDir: path.join(cacheDir, "locks"), }, }); } function prepareMocks( mockPort: MockPortBinding, nodeCapabilities?: MockNodeOptions["capabilities"], ): { mockController: MockController; mockNode: MockNode; } { const mockController = new MockController({ homeId: 0x7e570001, ownNodeId: 1, serial: mockPort, }); const mockNode = new MockNode({ id: 2, controller: mockController, capabilities: nodeCapabilities, }); mockController.addNode(mockNode); // Apply default behaviors that are required for interacting with the driver correctly mockController.defineBehavior(...createDefaultMockControllerBehaviors()); mockNode.defineBehavior(...createDefaultMockNodeBehaviors()); return { mockController, mockNode }; } interface IntegrationTestOptions { /** Enable debugging for this integration tests. When enabled, a driver logfile will be written and the test directory will not be deleted after each test. Default: false */ debug?: boolean; /** If given, the files from this directory will be copied into the test cache directory prior to starting the driver. */ provisioningDirectory?: string; /** Whether the recorded messages and frames should be cleared before executing the test body. Default: true. */ clearMessageStatsBeforeTest?: boolean; nodeCapabilities?: MockNodeOptions["capabilities"]; customSetup?: ( driver: Driver, mockController: MockController, mockNode: MockNode, ) => Promise<void>; testBody: ( driver: Driver, node: ZWaveNode, mockController: MockController, mockNode: MockNode, ) => Promise<void>; additionalDriverOptions?: Partial<ZWaveOptions>; } export interface IntegrationTestFn { (name: string, options: IntegrationTestOptions): void; } export interface IntegrationTest extends IntegrationTestFn { /** Only runs the tests inside this `integrationTest` suite for the current file */ only: IntegrationTestFn; /** Skips running the tests inside this `integrationTest` suite for the current file */ skip: IntegrationTestFn; } function suite(options: IntegrationTestOptions) { const { nodeCapabilities, customSetup, testBody, debug = false, provisioningDirectory, clearMessageStatsBeforeTest = true, additionalDriverOptions, } = options; let driver: Driver; let node: ZWaveNode; let mockPort: MockPortBinding; let continueStartup: () => void; let mockController: MockController; let mockNode: MockNode; const cacheDir = path.join( os.tmpdir(), `zjs_test_cache_${crypto.randomBytes(4).toString("hex")}`, ); beforeEach(async () => { if (debug) { console.log(`Running integration test in directory ${cacheDir}`); } // Make sure every test is starting fresh await fs.emptyDir(cacheDir).catch(() => {}); // And potentially provision the cache if (provisioningDirectory) { await fs.copy(provisioningDirectory, cacheDir); } ({ driver, continueStartup, mockPort } = await prepareDriver( cacheDir, debug, additionalDriverOptions, )); ({ mockController, mockNode } = prepareMocks( mockPort, nodeCapabilities, )); if (customSetup) { await customSetup(driver, mockController, mockNode); } return new Promise<void>((resolve) => { driver.once("driver ready", () => { // Test code goes here node = driver.controller.nodes.getOrThrow(mockNode.id); node.once("ready", () => { if (clearMessageStatsBeforeTest) { mockNode.clearReceivedControllerFrames(); mockNode.clearSentControllerFrames(); mockController.clearReceivedHostMessages(); } process.nextTick(resolve); }); }); continueStartup(); }); }, 30000); afterEach(async () => { await driver.destroy(); if (!debug) await fs.emptyDir(cacheDir).catch(() => {}); }); it("Test body", async () => { try { await testBody(driver, node, mockController, mockNode); } finally { // Give everything a chance to settle before destroying the driver. await wait(100); } }, 30000); } /** Performs an integration test with a real driver using a mock controller and one mock node */ export const integrationTest = (( name: string, options: IntegrationTestOptions, ): void => { describe(name, () => suite(options)); }) as IntegrationTest; integrationTest.only = (name: string, options: IntegrationTestOptions) => { describe.only(name, () => suite(options)); }; integrationTest.skip = (name: string, options: IntegrationTestOptions) => { describe.skip(name, () => suite(options)); };