import { BleControllerClient, BleFeatureNotSupportedError, BleTelemetryReading, BleWifiNetworkCredentials } from "../src/ble/BleControllerClient"; import { Logger } from "../src/observability/Logger"; import { BleControlBackend } from "../src/services/BleControlBackend"; class FakeBleClient implements BleControllerClient { public lastSet: { mac: string; portByte: number; speedLevel: number } | null = null; public lastWifiRecovery: { mac: string; credentials: BleWifiNetworkCredentials } | null = null; public wifiRecoveryMode: "ok" | "unsupported" = "ok"; public telemetry: BleTelemetryReading = { temperatureCelsius: 24.06, temperatureFahrenheit: 75.31, humidityPercent: 41.06, vpdKpa: 1.76, choosePort: 3, workType: 2, fanSpeedGuess: 7, receivedAtEpochSeconds: 1_700_000_123 }; public async verifyConnection(): Promise { return; } public async readTelemetry(): Promise { return this.telemetry; } public async setPortSpeed(macAddress: string, portByte: number, speedLevel: number): Promise { this.lastSet = { mac: macAddress, portByte, speedLevel }; } public async recoverWifi(macAddress: string, credentials: BleWifiNetworkCredentials): Promise { if (this.wifiRecoveryMode === "unsupported") { throw new BleFeatureNotSupportedError("wifi recovery unsupported"); } this.lastWifiRecovery = { mac: macAddress, credentials }; } } describe("BleControlBackend", () => { it("pairs and enforces allowed MAC list", async () => { const client = new FakeBleClient(); const backend = new BleControlBackend({ defaultMac: null, allowedMacs: ["58:8C:81:C6:FC:F6"], requestTimeoutMs: 10000, scanTimeoutMs: 10000, deviceType: 11, portBase: 1, logger: new Logger("error", "test"), client }); await expect(backend.pair("11:22:33:44:55:66")).rejects.toThrow("mac address is not in TY_BLE_ALLOWED_MACS"); const status = await backend.pair("58:8c:81:c6:fc:f6"); expect(status.controllerMac).toBe("58:8C:81:C6:FC:F6"); expect(status.connected).toBe(true); }); it("maps port numbers to device bytes and updates speed cache", async () => { const client = new FakeBleClient(); const backend = new BleControlBackend({ defaultMac: "58:8C:81:C6:FC:F6", allowedMacs: [], requestTimeoutMs: 10000, scanTimeoutMs: 10000, deviceType: 11, portBase: 1, logger: new Logger("error", "test"), client }); await backend.setPortSpeed(4, 9); expect(client.lastSet).toEqual({ mac: "58:8C:81:C6:FC:F6", portByte: 3, speedLevel: 9 }); const ports = await backend.getPorts(); expect(ports.find((p) => p.port === 4)?.currentSpeedLevel).toBe(9); }); it("builds a climate snapshot from BLE telemetry", async () => { const client = new FakeBleClient(); const backend = new BleControlBackend({ defaultMac: "58:8C:81:C6:FC:F6", allowedMacs: [], requestTimeoutMs: 10000, scanTimeoutMs: 10000, deviceType: 11, portBase: 1, logger: new Logger("error", "test"), client }); const snapshot = await backend.refreshTelemetry(); const controller = snapshot.controllers[0]; expect(controller?.temperatureCelsius).toBeCloseTo(24.06, 2); expect(controller?.relativeHumidityRatio).toBeCloseTo(0.4106, 4); expect(controller?.vpdKpa).toBeCloseTo(1.76, 2); expect(controller?.ports.find((p) => p.port === 4)?.currentSpeedLevel).toBe(7); }); it("sends wifi recovery credentials through BLE client", async () => { const client = new FakeBleClient(); const backend = new BleControlBackend({ defaultMac: "58:8C:81:C6:FC:F6", allowedMacs: [], requestTimeoutMs: 10000, scanTimeoutMs: 10000, deviceType: 11, portBase: 1, logger: new Logger("error", "test"), client }); const result = await backend.recoverWifi({ ssid: "atlas-net", password: "supersecret", hidden: true }); expect(result.queued).toBe(true); expect(client.lastWifiRecovery).toEqual({ mac: "58:8C:81:C6:FC:F6", credentials: { ssid: "atlas-net", password: "supersecret", hidden: true } }); }); it("returns 501 when wifi recovery is unsupported by BLE client", async () => { const client = new FakeBleClient(); client.wifiRecoveryMode = "unsupported"; const backend = new BleControlBackend({ defaultMac: "58:8C:81:C6:FC:F6", allowedMacs: [], requestTimeoutMs: 10000, scanTimeoutMs: 10000, deviceType: 11, portBase: 1, logger: new Logger("error", "test"), client }); await expect( backend.recoverWifi({ ssid: "atlas-net", password: "supersecret", hidden: false }) ).rejects.toThrow("wifi recovery over BLE is not implemented by this client yet"); }); });