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:
parent
e6188369f1
commit
20e6c8ce8c
1 changed files with 116 additions and 0 deletions
116
src/__tests__/pdf-rate-limit-coverage.test.ts
Normal file
116
src/__tests__/pdf-rate-limit-coverage.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue