test: add pdfRateLimit coverage tests for concurrency queue, per-key fairness, and stats

- getConcurrencyStats: verify pool state reporting (line 149)
- releaseConcurrencySlot waiter resolution: queued request gets slot on release (lines 116-117)
- per-key fairness: QUEUE_FULL when same key exceeds MAX_QUEUED_PER_KEY (lines 101-103)

886 tests passing (86 files), ZERO failures.
This commit is contained in:
OpenClaw Subagent 2026-03-21 14:05:03 +01:00
parent e6188369f1
commit 20e6c8ce8c

View file

@ -0,0 +1,116 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
// We need to reset module state between tests since pdfRateLimit uses module-level variables
async function freshImport() {
// Clear module cache to reset activePdfCount, pdfQueue, etc.
const modPath = "../middleware/pdfRateLimit.js";
// vitest handles this via vi.resetModules
const mod = await import("../middleware/pdfRateLimit.js");
return mod;
}
// Mock dependencies
vi.mock("../services/keys.js", () => ({
isProKey: (key: string) => key.startsWith("pro_"),
}));
vi.mock("../services/logger.js", () => ({
default: { warn: vi.fn(), info: vi.fn(), error: vi.fn() },
}));
describe("pdfRateLimit coverage gaps", () => {
beforeEach(() => {
vi.resetModules();
vi.useFakeTimers();
});
it("getConcurrencyStats returns current state", async () => {
const { getConcurrencyStats } = await import("../middleware/pdfRateLimit.js");
const stats = getConcurrencyStats();
expect(stats).toEqual({
activePdfCount: 0,
queueSize: 0,
maxConcurrent: 3,
maxQueue: 10,
});
});
it("releaseConcurrencySlot resolves a queued waiter", async () => {
const { pdfRateLimitMiddleware, getConcurrencyStats } = await import("../middleware/pdfRateLimit.js");
// Create mock req/res/next to get acquirePdfSlot/releasePdfSlot
const slots: { acquire: () => Promise<void>; release: () => void }[] = [];
function makeReqRes() {
const req: any = { apiKeyInfo: { key: "test_key_1" } };
const res: any = {
set: vi.fn(),
status: vi.fn().mockReturnThis(),
json: vi.fn(),
};
const next = vi.fn();
pdfRateLimitMiddleware(req, res, next);
return { acquire: req.acquirePdfSlot, release: req.releasePdfSlot };
}
// Fill all 3 concurrency slots
const s1 = makeReqRes();
await s1.acquire();
const s2 = makeReqRes();
await s2.acquire();
const s3 = makeReqRes();
await s3.acquire();
expect(getConcurrencyStats().activePdfCount).toBe(3);
// 4th request should queue
const s4 = makeReqRes();
let s4Resolved = false;
const s4Promise = s4.acquire().then(() => { s4Resolved = true; });
expect(getConcurrencyStats().queueSize).toBe(1);
expect(s4Resolved).toBe(false);
// Release one slot — should resolve the waiter (lines 116-117)
s1.release();
// Let microtasks flush
await vi.advanceTimersByTimeAsync(0);
await s4Promise;
expect(s4Resolved).toBe(true);
// Active count should still be 3 (waiter took the slot)
expect(getConcurrencyStats().activePdfCount).toBe(3);
expect(getConcurrencyStats().queueSize).toBe(0);
});
it("throws QUEUE_FULL when per-key queue limit reached (lines 101-103)", async () => {
const { pdfRateLimitMiddleware } = await import("../middleware/pdfRateLimit.js");
function makeReqRes(key: string) {
const req: any = { apiKeyInfo: { key } };
const res: any = {
set: vi.fn(),
status: vi.fn().mockReturnThis(),
json: vi.fn(),
};
const next = vi.fn();
pdfRateLimitMiddleware(req, res, next);
return { acquire: req.acquirePdfSlot, release: req.releasePdfSlot };
}
// Fill concurrency slots with different keys
await makeReqRes("other_1").acquire();
await makeReqRes("other_2").acquire();
await makeReqRes("other_3").acquire();
// Queue 3 requests for same key (MAX_QUEUED_PER_KEY = 3)
const sameKey = "same_key";
makeReqRes(sameKey).acquire(); // queued #1
makeReqRes(sameKey).acquire(); // queued #2
makeReqRes(sameKey).acquire(); // queued #3
// 4th from same key should throw QUEUE_FULL
await expect(makeReqRes(sameKey).acquire()).rejects.toThrow("QUEUE_FULL");
});
});