60 lines
2.0 KiB
TypeScript
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();
|
|
});
|
|
});
|