132 lines
4.1 KiB
TypeScript
132 lines
4.1 KiB
TypeScript
import http from "node:http";
|
|
|
|
import { ControlApiServer } from "../src/http/ControlApiServer";
|
|
import { ControlPortState, ControllerControlStatus } from "../src/domain/ControlTypes";
|
|
import { Logger } from "../src/observability/Logger";
|
|
import { ControllerControlBackend } from "../src/services/ControllerControlBackend";
|
|
import { RuleEngineService } from "../src/services/RuleEngineService";
|
|
|
|
class FakeBackend implements ControllerControlBackend {
|
|
private mac: string | null = null;
|
|
private ports: ControlPortState[] = [
|
|
{ port: 1, name: "Port 1", fanGroup: "outlet", currentSpeedLevel: 0, online: true, powerState: false },
|
|
{ port: 2, name: "Port 2", fanGroup: "inside_inlet", currentSpeedLevel: 0, online: true, powerState: false },
|
|
{ port: 3, name: "Port 3", fanGroup: "outside_inlet", currentSpeedLevel: 0, online: true, powerState: false },
|
|
{ port: 4, name: "Port 4", fanGroup: "interior", currentSpeedLevel: 0, online: true, powerState: false }
|
|
];
|
|
|
|
public async getStatus(): Promise<ControllerControlStatus> {
|
|
return {
|
|
mode: "ble",
|
|
backend: "fake",
|
|
controllerMac: this.mac,
|
|
connected: this.mac !== null,
|
|
paired: this.mac !== null,
|
|
telemetrySource: "ble",
|
|
lastSnapshotEpochSeconds: null,
|
|
capabilities: {
|
|
pairing: true,
|
|
portControl: true,
|
|
advancedRules: true
|
|
},
|
|
ports: this.ports,
|
|
notes: []
|
|
};
|
|
}
|
|
|
|
public async pair(macAddress: string): Promise<ControllerControlStatus> {
|
|
this.mac = macAddress;
|
|
return this.getStatus();
|
|
}
|
|
|
|
public async getPorts(): Promise<ControlPortState[]> {
|
|
return this.ports;
|
|
}
|
|
|
|
public async setPortSpeed(port: number, speedLevel: number): Promise<void> {
|
|
this.ports = this.ports.map((item) => {
|
|
if (item.port !== port) {
|
|
return item;
|
|
}
|
|
return {
|
|
...item,
|
|
currentSpeedLevel: speedLevel,
|
|
powerState: speedLevel > 0
|
|
};
|
|
});
|
|
}
|
|
}
|
|
|
|
async function requestJson(
|
|
port: number,
|
|
method: string,
|
|
path: string,
|
|
body?: unknown
|
|
): Promise<{ statusCode: number; payload: unknown }> {
|
|
return new Promise((resolve, reject) => {
|
|
const req = http.request(
|
|
{
|
|
host: "127.0.0.1",
|
|
port,
|
|
method,
|
|
path,
|
|
headers: body ? { "content-type": "application/json" } : undefined
|
|
},
|
|
(res) => {
|
|
const chunks: Buffer[] = [];
|
|
res.on("data", (chunk) => {
|
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
});
|
|
res.on("end", () => {
|
|
const text = Buffer.concat(chunks).toString("utf8");
|
|
let parsed: unknown = text;
|
|
try {
|
|
parsed = JSON.parse(text);
|
|
} catch {
|
|
// non-json response in tests should still be visible
|
|
}
|
|
resolve({
|
|
statusCode: res.statusCode ?? 0,
|
|
payload: parsed
|
|
});
|
|
});
|
|
}
|
|
);
|
|
|
|
req.on("error", reject);
|
|
if (body !== undefined) {
|
|
req.write(JSON.stringify(body));
|
|
}
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
describe("ControlApiServer", () => {
|
|
it("supports status, pair, and per-port speed writes", async () => {
|
|
const backend = new FakeBackend();
|
|
const rules = new RuleEngineService();
|
|
const logger = new Logger("error", "test");
|
|
const port = 19110;
|
|
|
|
const server = new ControlApiServer(port, backend, rules, logger, "58:8C:81:C6:FC:F6");
|
|
await server.start();
|
|
|
|
const statusBefore = await requestJson(port, "GET", "/api/v2/status");
|
|
expect(statusBefore.statusCode).toBe(200);
|
|
|
|
const pair = await requestJson(port, "POST", "/api/v2/pair", {});
|
|
expect(pair.statusCode).toBe(200);
|
|
|
|
const setSpeed = await requestJson(port, "POST", "/api/v2/ports/4/speed", { speed_level: 8 });
|
|
expect(setSpeed.statusCode).toBe(200);
|
|
|
|
const ports = await requestJson(port, "GET", "/api/v2/ports");
|
|
expect(ports.statusCode).toBe(200);
|
|
const parsed = ports.payload as { ports?: Array<{ port: number; currentSpeedLevel: number }> };
|
|
const interior = parsed.ports?.find((item) => item.port === 4);
|
|
expect(interior?.currentSpeedLevel).toBe(8);
|
|
|
|
await server.stop();
|
|
});
|
|
});
|