iobroker.roborock
Version:
141 lines (112 loc) • 5.12 kB
text/typescript
import { beforeEach, describe, expect, it } from "vitest";
import { requestsHandler } from "../requestsHandler"; // Real class
import { MockAdapter } from "./MockAdapter";
describe("Queue Deep Dive (requestsHandler)", () => {
let mockAdapter: MockAdapter;
let handler: requestsHandler;
beforeEach(() => {
mockAdapter = new MockAdapter();
// Mock sub-APIs required by requestsHandler
mockAdapter.mqtt_api = { ensureEndpoint: async () => "endpoint", isConnected: () => true, sendMessage: () => {}, clearIntervals: () => {} } as any;
mockAdapter.local_api = { isConnected: () => true, sendMessage: () => true, clearLocalDevicedTimeout: () => {} } as any;
mockAdapter.http_api = { getMatchedLocalKeys: () => new Map([["duid", Buffer.alloc(16)]]) } as any;
mockAdapter.getDeviceProtocolVersion = async () => "1.0";
handler = new requestsHandler(mockAdapter as any);
handler.startupFinished = true; // Bypass startup wait
});
it("should respect concurrency limit (10)", async () => {
const duid = "duid";
let activeRequests = 0;
let maxActive = 0;
// Mock message parser to avoid overhead
handler.messageParser.buildPayload = async () => "payload";
handler.messageParser.buildRoborockMessage = async () => Buffer.from("msg");
const manager = handler["globalManager"];
// We hijack manager.add to count executions
const originalAdd = manager.add.bind(manager);
manager.add = <T>(id: string, _task: (signal: AbortSignal) => Promise<T>, priority?: number): Promise<T> => {
return originalAdd(id, async () => {
activeRequests++;
maxActive = Math.max(maxActive, activeRequests);
await new Promise(res => setTimeout(res, 20));
activeRequests--;
return "ok" as unknown as T;
}, priority);
};
const promises = [];
for (let i = 0; i < 20; i++) {
promises.push(handler.sendRequest(duid, "get_status", []));
}
// Wait a bit for them to start
await new Promise(res => setTimeout(res, 10));
// Concurrency is 10.
expect(maxActive).to.be.at.most(11); // Allow 1 buffer for timing
expect(manager.queue.pending).to.be.above(0); // Should have items waiting
});
it("should prioritize high-priority requests", async () => {
// Force concurrency 1 to strictly test priority/serialization
// Need to import RequestManager class or mock it?
// RequestManager is not exported from requestsHandler.ts?
// It is NOT exported.
// So we cannot instantiate it easily.
// We can however modify the existing one.
const manager = handler["globalManager"];
manager.queue.concurrency = 1;
const executionOrder: string[] = [];
// Mock message parser to avoid overhead
handler.messageParser.buildPayload = async () => "payload";
handler.messageParser.buildRoborockMessage = async () => Buffer.from("msg");
// We modify queue behavior to synchronous push to order list
const originalAdd = manager.add.bind(manager);
manager.add = <T>(id: string, _task: (signal: AbortSignal) => Promise<T>, priority?: number): Promise<T> => {
// Identifier hack to trace ordering
const name = id.includes("blocker") ? "blocker" : (id.includes("low") ? "low" : "high");
return originalAdd(id, async () => {
if (name === "blocker") await new Promise(res => setTimeout(res, 50));
executionOrder.push(name);
return "ok" as unknown as T;
}, priority);
};
// Block the queue first with a slow task
// sendRequest generates an ID, so we can't easily control the name unless we mock sendRequest or params?
// Let's use manager.add directly for this test as we want to test PQueue behavior wrapper in Manager
// Blocker
handler["globalManager"].add("req_blocker", async () => {
await new Promise(res => setTimeout(res, 50));
executionOrder.push("blocker");
return "ok";
}, 0);
// Low Priority
handler["globalManager"].add("req_low", async () => {
executionOrder.push("low");
return "ok";
}, 0);
// High Priority
handler["globalManager"].add("req_high", async () => {
executionOrder.push("high");
return "ok";
}, 10);
await manager.onIdle();
// Expect: blocker (started), then high, then low
expect(executionOrder).to.deep.equal(["blocker", "high", "low"]);
});
it("does not reuse request IDs as local TCP transport message IDs", async () => {
let payloadRequestId: number | undefined;
let transportSequenceId: number | undefined;
mockAdapter.getDeviceProtocolVersion = async () => "L01";
handler.messageParser.buildPayload = async (_protocol, messageID) => {
payloadRequestId = messageID;
return "payload";
};
handler.messageParser.buildRoborockMessage = async (_duid, _protocol, _timestamp, _payload, _version, sequenceId) => {
transportSequenceId = sequenceId;
return Buffer.from("msg");
};
const requestPromise = handler.sendRequest("duid", "get_status", []);
await new Promise(res => setTimeout(res, 10));
expect(payloadRequestId).to.equal(301);
expect(transportSequenceId).to.equal(1);
handler.resolvePendingRequest(301, ["ok"], 4, "duid", "TCP");
await expect(requestPromise).resolves.to.deep.equal(["ok"]);
});
});