Files
suaveplan/packages/foundation/flow/rate-limiter/src/schemas/rate-limiter.schemas.test.ts
2026-05-27 23:47:53 +01:00

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);
});
});