alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
169 lines (134 loc) • 4.93 kB
text/typescript
import { Alepha } from "alepha";
import { AlephaCache } from "alepha/cache";
import { $action, AlephaServer } from "alepha/server";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { AlephaServerRateLimit } from "../index.ts";
describe("Action Rate Limiting", () => {
let alepha: Alepha;
class TestApp {
// Action with rate limiting enabled via options
limitedAction = $action({
rateLimit: { max: 2, windowMs: 1000 },
handler: () => "success",
});
// Action with custom key generator
userBasedAction = $action({
rateLimit: {
max: 3,
windowMs: 2000,
keyGenerator: (req) => `user:${req.headers["user-id"] || "anonymous"}`,
},
handler: () => "user success",
});
// Action without rate limiting
unlimitedAction = $action({
handler: () => "unlimited success",
});
}
beforeEach(async () => {
alepha = Alepha.create()
.with(AlephaCache)
.with(AlephaServer)
.with(AlephaServerRateLimit)
.with(TestApp);
await alepha.start();
});
afterEach(async () => {
await alepha.stop();
});
describe("Action rateLimit option", () => {
it("should allow requests within rate limit", async () => {
const app = alepha.inject(TestApp);
const result1 = await app.limitedAction.run({});
expect(result1).toBe("success");
const result2 = await app.limitedAction.run({});
expect(result2).toBe("success");
});
it("should block requests exceeding rate limit", async () => {
const app = alepha.inject(TestApp);
// First two requests should succeed
await app.limitedAction.run({});
await app.limitedAction.run({});
// Third request should be rate limited
await expect(app.limitedAction.run({})).rejects.toThrow(
"Too Many Requests",
);
});
it("should use custom key generator", async () => {
const app = alepha.inject(TestApp);
// Test that the custom key generator allows more requests per user
// since each user gets their own rate limit bucket
const result1 = await app.userBasedAction.run({});
expect(result1).toBe("user success");
const result2 = await app.userBasedAction.run({});
expect(result2).toBe("user success");
const result3 = await app.userBasedAction.run({});
expect(result3).toBe("user success");
// Fourth request should be blocked (limit is 3)
await expect(app.userBasedAction.run({})).rejects.toThrow(
"Too Many Requests",
);
});
it("should not affect unlimited actions", async () => {
const app = alepha.inject(TestApp);
// Should be able to make many requests to unlimited action
for (let i = 0; i < 10; i++) {
const result = await app.unlimitedAction.run({});
expect(result).toBe("unlimited success");
}
});
it("should reset rate limit after window expires", async () => {
const app = alepha.inject(TestApp);
// Use up the rate limit
await app.limitedAction.run({});
await app.limitedAction.run({});
// Should be blocked
await expect(app.limitedAction.run({})).rejects.toThrow(
"Too Many Requests",
);
// Wait for the window to expire (1000ms + buffer)
await new Promise((resolve) => setTimeout(resolve, 1100));
// Should be allowed again
const result = await app.limitedAction.run({});
expect(result).toBe("success");
});
});
describe("Integration", () => {
it("should integrate rate limiting with action hooks", async () => {
const app = alepha.inject(TestApp);
// Just verify that the hook is working by checking that rate limiting is applied
await app.limitedAction.run({});
await app.limitedAction.run({});
// Third request should trigger rate limiting
await expect(app.limitedAction.run({})).rejects.toThrow(
"Too Many Requests",
);
});
});
describe("Performance and edge cases", () => {
it("should handle concurrent requests correctly", async () => {
const app = alepha.inject(TestApp);
// Test sequential requests to avoid race conditions in testing
let successCount = 0;
let rateLimitedCount = 0;
for (let i = 0; i < 5; i++) {
try {
await app.limitedAction.run({});
successCount++;
} catch (e: any) {
if (e.message === "Too Many Requests") {
rateLimitedCount++;
}
}
}
expect(successCount).toBe(2);
expect(rateLimitedCount).toBe(3);
});
it("should handle missing IP gracefully", async () => {
const app = alepha.inject(TestApp);
// Should still work with default IP handling
const result = await app.limitedAction.run({});
expect(result).toBe("success");
});
});
});