220 lines
6.2 KiB
TypeScript
220 lines
6.2 KiB
TypeScript
import { describe, expect, it } from "@suaveplan/testing/runner";
|
|
import {
|
|
FixedWindowOptionsSchema,
|
|
MemoryStorageOptionsSchema,
|
|
RateLimitConditionSchema,
|
|
RateLimitConfigSchema,
|
|
RateLimitContextSchema,
|
|
RateLimiterOptionsSchema,
|
|
RateLimitRuleSchema,
|
|
SlidingWindowOptionsSchema,
|
|
TokenBucketOptionsSchema,
|
|
} from "./rate-limiter.schemas.js";
|
|
|
|
describe("RateLimitConfigSchema", () => {
|
|
it("validates valid config", { timeout: 5000 }, () => {
|
|
expect(
|
|
RateLimitConfigSchema.safeParse({ limit: 100, window: 60_000 })
|
|
.success
|
|
).toBe(true);
|
|
});
|
|
|
|
it("rejects missing limit", { timeout: 5000 }, () => {
|
|
expect(RateLimitConfigSchema.safeParse({ window: 1000 }).success).toBe(
|
|
false
|
|
);
|
|
});
|
|
|
|
it("rejects non-integer limit", { timeout: 5000 }, () => {
|
|
expect(
|
|
RateLimitConfigSchema.safeParse({ limit: 1.5, window: 1000 })
|
|
.success
|
|
).toBe(false);
|
|
});
|
|
|
|
it("accepts optional id and description", { timeout: 5000 }, () => {
|
|
const r = RateLimitConfigSchema.safeParse({
|
|
limit: 10,
|
|
window: 1000,
|
|
id: "my-rule",
|
|
description: "test",
|
|
});
|
|
expect(r.success).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("RateLimitConditionSchema", () => {
|
|
it("validates eq condition", { timeout: 5000 }, () => {
|
|
expect(
|
|
RateLimitConditionSchema.safeParse({
|
|
field: "userId",
|
|
operator: "eq",
|
|
value: "admin",
|
|
}).success
|
|
).toBe(true);
|
|
});
|
|
|
|
it("rejects unknown operator", { timeout: 5000 }, () => {
|
|
expect(
|
|
RateLimitConditionSchema.safeParse({
|
|
field: "x",
|
|
operator: "gt",
|
|
value: 1,
|
|
}).success
|
|
).toBe(false);
|
|
});
|
|
|
|
it("accepts all operators", { timeout: 5000 }, () => {
|
|
for (const op of ["eq", "neq", "in", "nin", "matches"]) {
|
|
expect(
|
|
RateLimitConditionSchema.safeParse({
|
|
field: "f",
|
|
operator: op,
|
|
value: "v",
|
|
}).success
|
|
).toBe(true);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("RateLimitRuleSchema", () => {
|
|
it("validates rule with bypass", { timeout: 5000 }, () => {
|
|
const r = RateLimitRuleSchema.safeParse({
|
|
limit: 0,
|
|
window: 1000,
|
|
bypass: true,
|
|
priority: 100,
|
|
});
|
|
expect(r.success).toBe(true);
|
|
});
|
|
|
|
it("validates rule with conditions", { timeout: 5000 }, () => {
|
|
const r = RateLimitRuleSchema.safeParse({
|
|
limit: 5,
|
|
window: 1000,
|
|
conditions: [{ field: "ip", operator: "eq", value: "1.2.3.4" }],
|
|
});
|
|
expect(r.success).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("RateLimitContextSchema", () => {
|
|
it("validates minimal context", { timeout: 5000 }, () => {
|
|
expect(
|
|
RateLimitContextSchema.safeParse({ key: "user:1" }).success
|
|
).toBe(true);
|
|
});
|
|
|
|
it("rejects empty key", { timeout: 5000 }, () => {
|
|
expect(RateLimitContextSchema.safeParse({ key: "" }).success).toBe(
|
|
false
|
|
);
|
|
});
|
|
|
|
it("validates full context", { timeout: 5000 }, () => {
|
|
expect(
|
|
RateLimitContextSchema.safeParse({
|
|
key: "k",
|
|
tenantId: "acme",
|
|
userId: "u1",
|
|
ip: "1.1.1.1",
|
|
path: "/api",
|
|
method: "GET",
|
|
metadata: { custom: 1 },
|
|
}).success
|
|
).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("RateLimiterOptionsSchema", () => {
|
|
it("validates minimal options", { timeout: 5000 }, () => {
|
|
expect(
|
|
RateLimiterOptionsSchema.safeParse({
|
|
defaultConfig: { limit: 10, window: 1000 },
|
|
}).success
|
|
).toBe(true);
|
|
});
|
|
|
|
it("validates full options", { timeout: 5000 }, () => {
|
|
expect(
|
|
RateLimiterOptionsSchema.safeParse({
|
|
defaultConfig: { limit: 100, window: 60_000 },
|
|
rules: [],
|
|
enableTenantLimits: true,
|
|
tenantMultipliers: { enterprise: 5 },
|
|
keyPrefix: "rl",
|
|
}).success
|
|
).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("TokenBucketOptionsSchema", () => {
|
|
it("validates valid options", { timeout: 5000 }, () => {
|
|
expect(
|
|
TokenBucketOptionsSchema.safeParse({
|
|
capacity: 100,
|
|
refillRate: 0.1,
|
|
}).success
|
|
).toBe(true);
|
|
});
|
|
|
|
it("accepts initialTokens", { timeout: 5000 }, () => {
|
|
expect(
|
|
TokenBucketOptionsSchema.safeParse({
|
|
capacity: 100,
|
|
refillRate: 0.1,
|
|
initialTokens: 50,
|
|
}).success
|
|
).toBe(true);
|
|
});
|
|
|
|
it("rejects negative capacity", { timeout: 5000 }, () => {
|
|
expect(
|
|
TokenBucketOptionsSchema.safeParse({
|
|
capacity: -1,
|
|
refillRate: 0.1,
|
|
}).success
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("SlidingWindowOptionsSchema", () => {
|
|
it("validates valid options", { timeout: 5000 }, () => {
|
|
expect(
|
|
SlidingWindowOptionsSchema.safeParse({
|
|
limit: 100,
|
|
windowMs: 60_000,
|
|
}).success
|
|
).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("FixedWindowOptionsSchema", () => {
|
|
it("validates valid options", { timeout: 5000 }, () => {
|
|
expect(
|
|
FixedWindowOptionsSchema.safeParse({
|
|
limit: 100,
|
|
windowMs: 60_000,
|
|
}).success
|
|
).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("MemoryStorageOptionsSchema", () => {
|
|
it("validates empty options", { timeout: 5000 }, () => {
|
|
expect(MemoryStorageOptionsSchema.safeParse({}).success).toBe(true);
|
|
});
|
|
|
|
it("validates maxSize", { timeout: 5000 }, () => {
|
|
expect(
|
|
MemoryStorageOptionsSchema.safeParse({ maxSize: 5000 }).success
|
|
).toBe(true);
|
|
});
|
|
|
|
it("rejects non-positive maxSize", { timeout: 5000 }, () => {
|
|
expect(
|
|
MemoryStorageOptionsSchema.safeParse({ maxSize: 0 }).success
|
|
).toBe(false);
|
|
});
|
|
});
|