feat(tests): improve pdfRateLimit middleware test coverage
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 18m54s
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:
parent
14181d17a7
commit
2bdf93d09f
1 changed files with 155 additions and 0 deletions
155
src/__tests__/pdfRateLimit-coverage.test.ts
Normal file
155
src/__tests__/pdfRateLimit-coverage.test.ts
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue