Files
Epicure/apps/web/lib/__tests__/rate-limit.test.ts
T
2026-07-01 11:10:37 +02:00

60 lines
2.0 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
const pipelineChain = vi.hoisted(() => ({
zadd: vi.fn().mockReturnThis(),
zremrangebyscore: vi.fn().mockReturnThis(),
zcard: vi.fn().mockReturnThis(),
expire: vi.fn().mockReturnThis(),
exec: vi.fn(),
}));
vi.mock("../redis", () => ({
getRedis: vi.fn(() => ({ pipeline: vi.fn(() => pipelineChain) })),
}));
vi.mock("next/server", () => ({
NextResponse: {
json: vi.fn((body: unknown, init?: { status?: number }) => ({ body, init, isNextResponse: true })),
},
}));
import { NextResponse } from "next/server";
const { applyRateLimit } = await import("../rate-limit");
beforeEach(() => {
vi.clearAllMocks();
});
describe("applyRateLimit", () => {
it("returns null when under the limit", async () => {
pipelineChain.exec.mockResolvedValue([null, null, [null, 3], null]);
const result = await applyRateLimit("test-key", 10, 60);
expect(result).toBeNull();
});
it("returns 429 response when over the limit", async () => {
pipelineChain.exec.mockResolvedValue([null, null, [null, 11], null]);
const result = await applyRateLimit("test-key", 10, 60);
expect(result).not.toBeNull();
expect(NextResponse.json).toHaveBeenCalledWith(
expect.objectContaining({ error: expect.any(String) }),
expect.objectContaining({ status: 429 })
);
});
it("returns null when exactly at limit (count === limit is allowed)", async () => {
pipelineChain.exec.mockResolvedValue([null, null, [null, 10], null]);
const result = await applyRateLimit("test-key", 10, 60);
expect(result).toBeNull();
});
it("calls pipeline with zadd, zremrangebyscore, zcard, expire", async () => {
pipelineChain.exec.mockResolvedValue([null, null, [null, 1], null]);
await applyRateLimit("my-key", 5, 30);
expect(pipelineChain.zadd).toHaveBeenCalled();
expect(pipelineChain.zremrangebyscore).toHaveBeenCalled();
expect(pipelineChain.zcard).toHaveBeenCalled();
expect(pipelineChain.expire).toHaveBeenCalled();
});
});