feat(tests): improve pdfRateLimit middleware test coverage
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 18m54s

- Add tests for per-key queue fairness rejection path (MAX_QUEUED_PER_KEY)
- Add tests for cleanupExpiredEntries behavior and automatic cleanup
- Cover edge cases with unknown API keys
- Tests improve branch coverage for lines around 103, 116-117, 149
This commit is contained in:
OpenClaw Subagent 2026-03-14 08:12:20 +01:00
parent 14181d17a7
commit 2bdf93d09f

View file

@ -0,0 +1,155 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { isProKey } from "../services/keys.js";
// Tests to improve coverage for pdfRateLimit.ts
// Target: per-key queue fairness rejection and cleanupExpiredEntries behavior
const mockNext = vi.fn();
const headers: Record<string, string> = {};
const mockSet = vi.fn((k: string, v: string) => { headers[k] = v; });
const mockJson = vi.fn();
const mockStatus = vi.fn(() => ({ json: mockJson }));
function makeReq(key = "test-key"): any {
return {
apiKeyInfo: { key, tier: "free", email: "t@t.com", createdAt: "2025-01-01" },
headers: {},
};
}
function makeRes(): any {
Object.keys(headers).forEach((k) => delete headers[k]);
mockSet.mockClear();
mockJson.mockClear();
mockStatus.mockClear();
return { set: mockSet, status: mockStatus, json: mockJson };
}
describe("pdfRateLimit middleware - additional coverage", () => {
let pdfRateLimitMiddleware: any;
beforeEach(async () => {
vi.clearAllMocks();
vi.resetModules();
const mod = await import("../middleware/pdfRateLimit.js");
pdfRateLimitMiddleware = mod.pdfRateLimitMiddleware;
});
it("should reject per-key queue fairness when MAX_QUEUED_PER_KEY (3) exceeded", async () => {
vi.mocked(isProKey).mockReturnValue(false);
// Fill up 3 concurrent slots with different keys
const concurrentReqs = [];
for (let i = 0; i < 3; i++) {
const req = makeReq(`concurrent-${i}`);
const res = makeRes();
pdfRateLimitMiddleware(req, res, mockNext);
concurrentReqs.push(req);
await (req as any).acquirePdfSlot(); // Acquire but don't release
}
// Now try to queue 4 requests with the same key (should only allow 3 per key)
const sameKeyPromises = [];
const sameKey = "same-key";
// Queue 3 requests with the same key (this should work)
for (let i = 0; i < 3; i++) {
const req = makeReq(sameKey);
const res = makeRes();
pdfRateLimitMiddleware(req, res, mockNext);
sameKeyPromises.push((req as any).acquirePdfSlot());
}
// Try to queue a 4th request with the same key - this should fail with QUEUE_FULL
const req4th = makeReq(sameKey);
const res4th = makeRes();
pdfRateLimitMiddleware(req4th, res4th, mockNext);
await expect((req4th as any).acquirePdfSlot()).rejects.toThrow("QUEUE_FULL");
});
it("should call cleanupExpiredEntries and remove expired rate limit entries", async () => {
vi.mocked(isProKey).mockReturnValue(false);
vi.useFakeTimers();
try {
// Create a rate limit entry
const req = makeReq("cleanup-test-key");
const res = makeRes();
pdfRateLimitMiddleware(req, res, mockNext);
// Verify it was allowed (first request)
expect(mockNext).toHaveBeenCalled();
mockNext.mockClear();
// Advance time past the rate limit window (60s + 1ms)
vi.advanceTimersByTime(60_001);
// Make another request - this should trigger cleanup and reset the rate limit
const req2 = makeReq("cleanup-test-key");
const res2 = makeRes();
pdfRateLimitMiddleware(req2, res2, mockNext);
// Should be allowed again since cleanup removed the expired entry
expect(mockNext).toHaveBeenCalled();
expect(mockSet).toHaveBeenCalledWith("X-RateLimit-Remaining", "9"); // Should be 9 (10-1)
} finally {
vi.useRealTimers();
}
});
it("should test automatic cleanup interval calls cleanupExpiredEntries", async () => {
vi.useFakeTimers();
try {
// Import the module to trigger the setInterval
const mod = await import("../middleware/pdfRateLimit.js");
pdfRateLimitMiddleware = mod.pdfRateLimitMiddleware;
vi.mocked(isProKey).mockReturnValue(false);
// Create some rate limit entries that will expire
const req1 = makeReq("auto-cleanup-1");
const res1 = makeRes();
pdfRateLimitMiddleware(req1, res1, mockNext);
const req2 = makeReq("auto-cleanup-2");
const res2 = makeRes();
pdfRateLimitMiddleware(req2, res2, mockNext);
// Advance past expiration
vi.advanceTimersByTime(70_000); // 70 seconds
// Trigger the automatic cleanup by advancing the interval timer
vi.advanceTimersByTime(60_000); // Cleanup runs every 60s
// Create a new request - should start fresh since old entries were cleaned up
const req3 = makeReq("auto-cleanup-1");
const res3 = makeRes();
pdfRateLimitMiddleware(req3, res3, mockNext);
expect(mockNext).toHaveBeenCalled();
} finally {
vi.useRealTimers();
}
});
it("should handle unknown api key in middleware", () => {
vi.mocked(isProKey).mockReturnValue(false);
// Request without apiKeyInfo (should default to "unknown")
const req = {
apiKeyInfo: undefined,
headers: {},
};
const res = makeRes();
pdfRateLimitMiddleware(req, res, mockNext);
// Should still set headers and call next (using "unknown" as key)
expect(mockSet).toHaveBeenCalledWith("X-RateLimit-Limit", "10");
expect(mockNext).toHaveBeenCalled();
});
});