165 lines
4.9 KiB
TypeScript
165 lines
4.9 KiB
TypeScript
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<void> {
|
|
return;
|
|
}
|
|
|
|
public async readTelemetry(): Promise<BleTelemetryReading> {
|
|
return this.telemetry;
|
|
}
|
|
|
|
public async setPortSpeed(macAddress: string, portByte: number, speedLevel: number): Promise<void> {
|
|
this.lastSet = { mac: macAddress, portByte, speedLevel };
|
|
}
|
|
|
|
public async recoverWifi(macAddress: string, credentials: BleWifiNetworkCredentials): Promise<void> {
|
|
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");
|
|
});
|
|
});
|