UNPKG

iobroker.roborock

Version:
141 lines (112 loc) 5.12 kB
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"]); }); });