@dangao/bun-server 1.8.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/tests/auth/auth-decorators.test.ts +241 -0
- package/tests/auth/oauth2-service.test.ts +318 -0
- package/tests/cache/cache-decorators-extended.test.ts +272 -0
- package/tests/cache/cache-interceptors.test.ts +534 -0
- package/tests/cache/cache-service-proxy.test.ts +246 -0
- package/tests/cache/memory-cache-store.test.ts +155 -0
- package/tests/cache/redis-cache-store.test.ts +199 -0
- package/tests/config/config-center-integration.test.ts +334 -0
- package/tests/config/config-module-extended.test.ts +165 -0
- package/tests/controller/param-binder.test.ts +333 -0
- package/tests/error/error-handler.test.ts +166 -57
- package/tests/error/i18n-extended.test.ts +105 -0
- package/tests/events/event-listener-scanner.test.ts +114 -0
- package/tests/events/event-module.test.ts +133 -302
- package/tests/extensions/logger-module.test.ts +158 -0
- package/tests/files/file-storage.test.ts +136 -0
- package/tests/interceptor/base-interceptor.test.ts +605 -0
- package/tests/interceptor/builtin/cache-interceptor.test.ts +233 -86
- package/tests/interceptor/builtin/log-interceptor.test.ts +469 -0
- package/tests/interceptor/builtin/permission-interceptor.test.ts +219 -120
- package/tests/interceptor/interceptor-chain.test.ts +241 -189
- package/tests/interceptor/interceptor-metadata.test.ts +221 -0
- package/tests/microservice/circuit-breaker.test.ts +221 -0
- package/tests/microservice/service-client-decorators.test.ts +86 -0
- package/tests/microservice/service-client-interceptors.test.ts +274 -0
- package/tests/microservice/service-registry-decorators.test.ts +147 -0
- package/tests/microservice/tracer.test.ts +213 -0
- package/tests/microservice/tracing-collectors.test.ts +168 -0
- package/tests/middleware/builtin/middleware-builtin-extended.test.ts +237 -0
- package/tests/middleware/builtin/rate-limit.test.ts +257 -0
- package/tests/middleware/middleware-decorators.test.ts +222 -0
- package/tests/middleware/middleware-pipeline.test.ts +160 -0
- package/tests/queue/queue-decorators.test.ts +139 -0
- package/tests/queue/queue-service.test.ts +191 -0
- package/tests/request/body-parser-extended.test.ts +291 -0
- package/tests/request/request-wrapper.test.ts +319 -0
- package/tests/router/router-decorators.test.ts +260 -0
- package/tests/router/router-extended.test.ts +298 -0
- package/tests/security/guards/reflector.test.ts +188 -0
- package/tests/security/security-filter.test.ts +182 -0
- package/tests/security/security-module-extended.test.ts +133 -0
- package/tests/session/memory-session-store.test.ts +172 -0
- package/tests/session/session-decorators.test.ts +163 -0
- package/tests/swagger/ui.test.ts +212 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach } from 'bun:test';
|
|
2
|
+
import 'reflect-metadata';
|
|
3
|
+
|
|
4
|
+
import { createCorsMiddleware } from '../../../src/middleware/builtin/cors';
|
|
5
|
+
import { createLoggerMiddleware } from '../../../src/middleware/builtin/logger';
|
|
6
|
+
import { createStaticFileMiddleware } from '../../../src/middleware/builtin/static-file';
|
|
7
|
+
import { createFileUploadMiddleware } from '../../../src/middleware/builtin/file-upload';
|
|
8
|
+
import { Context } from '../../../src/core/context';
|
|
9
|
+
import { Container } from '../../../src/di/container';
|
|
10
|
+
|
|
11
|
+
describe('CORS Middleware', () => {
|
|
12
|
+
test('should add CORS headers to context', async () => {
|
|
13
|
+
const middleware = createCorsMiddleware({
|
|
14
|
+
origin: '*',
|
|
15
|
+
methods: ['GET', 'POST'],
|
|
16
|
+
allowedHeaders: ['Content-Type'],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const request = new Request('http://localhost/api/data', {
|
|
20
|
+
headers: { Origin: 'http://example.com' },
|
|
21
|
+
});
|
|
22
|
+
const context = new Context(request, new Container());
|
|
23
|
+
|
|
24
|
+
await middleware(context, async () => new Response('ok'));
|
|
25
|
+
|
|
26
|
+
// CORS middleware sets headers on context.responseHeaders
|
|
27
|
+
expect(context.responseHeaders.get('Access-Control-Allow-Origin')).toBe('*');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('should handle preflight OPTIONS request', async () => {
|
|
31
|
+
const middleware = createCorsMiddleware({
|
|
32
|
+
origin: 'http://example.com',
|
|
33
|
+
methods: ['GET', 'POST', 'PUT'],
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const request = new Request('http://localhost/api/data', {
|
|
37
|
+
method: 'OPTIONS',
|
|
38
|
+
headers: { Origin: 'http://example.com' },
|
|
39
|
+
});
|
|
40
|
+
const context = new Context(request, new Container());
|
|
41
|
+
|
|
42
|
+
const response = await middleware(context, async () => new Response('should not reach'));
|
|
43
|
+
|
|
44
|
+
expect(response.status).toBe(204);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('should use array of origins', async () => {
|
|
48
|
+
const middleware = createCorsMiddleware({
|
|
49
|
+
origin: ['http://allowed.com', 'http://example.com'],
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const request = new Request('http://localhost/api/data', {
|
|
53
|
+
headers: { Origin: 'http://allowed.com' },
|
|
54
|
+
});
|
|
55
|
+
const context = new Context(request, new Container());
|
|
56
|
+
|
|
57
|
+
await middleware(context, async () => new Response('ok'));
|
|
58
|
+
|
|
59
|
+
expect(context.responseHeaders.get('Access-Control-Allow-Origin')).toBe('http://allowed.com');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('should set credentials header when enabled', async () => {
|
|
63
|
+
const middleware = createCorsMiddleware({
|
|
64
|
+
origin: '*',
|
|
65
|
+
credentials: true,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const request = new Request('http://localhost/api/data', {
|
|
69
|
+
headers: { Origin: 'http://example.com' },
|
|
70
|
+
});
|
|
71
|
+
const context = new Context(request, new Container());
|
|
72
|
+
|
|
73
|
+
await middleware(context, async () => new Response('ok'));
|
|
74
|
+
|
|
75
|
+
expect(context.responseHeaders.get('Access-Control-Allow-Credentials')).toBe('true');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('should set exposed headers', async () => {
|
|
79
|
+
const middleware = createCorsMiddleware({
|
|
80
|
+
origin: '*',
|
|
81
|
+
exposedHeaders: ['X-Custom-Header'],
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const request = new Request('http://localhost/api/data');
|
|
85
|
+
const context = new Context(request, new Container());
|
|
86
|
+
|
|
87
|
+
await middleware(context, async () => new Response('ok'));
|
|
88
|
+
|
|
89
|
+
expect(context.responseHeaders.get('Access-Control-Expose-Headers')).toBe('X-Custom-Header');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('Logger Middleware', () => {
|
|
94
|
+
test('should log request information', async () => {
|
|
95
|
+
const logs: string[] = [];
|
|
96
|
+
const middleware = createLoggerMiddleware({
|
|
97
|
+
logger: (msg) => logs.push(msg),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const request = new Request('http://localhost/api/data');
|
|
101
|
+
const context = new Context(request, new Container());
|
|
102
|
+
|
|
103
|
+
await middleware(context, async () => new Response('ok'));
|
|
104
|
+
|
|
105
|
+
expect(logs.length).toBeGreaterThan(0);
|
|
106
|
+
expect(logs.some((log) => log.includes('/api/data'))).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('should log status code', async () => {
|
|
110
|
+
const logs: string[] = [];
|
|
111
|
+
const middleware = createLoggerMiddleware({
|
|
112
|
+
logger: (msg) => logs.push(msg),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const request = new Request('http://localhost/api/test');
|
|
116
|
+
const context = new Context(request, new Container());
|
|
117
|
+
|
|
118
|
+
await middleware(context, async () => new Response('ok', { status: 200 }));
|
|
119
|
+
|
|
120
|
+
expect(logs.some((log) => log.includes('200'))).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('should use custom prefix', async () => {
|
|
124
|
+
const logs: string[] = [];
|
|
125
|
+
const middleware = createLoggerMiddleware({
|
|
126
|
+
prefix: '[MyApp]',
|
|
127
|
+
logger: (msg) => logs.push(msg),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const request = new Request('http://localhost/test');
|
|
131
|
+
const context = new Context(request, new Container());
|
|
132
|
+
|
|
133
|
+
await middleware(context, async () => new Response('ok'));
|
|
134
|
+
|
|
135
|
+
expect(logs.some((log) => log.includes('[MyApp]'))).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('Static File Middleware', () => {
|
|
140
|
+
test('should return 404 for non-existent file', async () => {
|
|
141
|
+
const middleware = createStaticFileMiddleware({
|
|
142
|
+
root: '/tmp/nonexistent-dir',
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const request = new Request('http://localhost/file.txt');
|
|
146
|
+
const context = new Context(request, new Container());
|
|
147
|
+
|
|
148
|
+
const response = await middleware(context, async () => new Response('not found', { status: 404 }));
|
|
149
|
+
|
|
150
|
+
// Should pass to next middleware if file not found
|
|
151
|
+
expect(response.status).toBe(404);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('should use custom prefix', async () => {
|
|
155
|
+
const middleware = createStaticFileMiddleware({
|
|
156
|
+
root: '/tmp',
|
|
157
|
+
prefix: '/static',
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const request = new Request('http://localhost/static/file.txt');
|
|
161
|
+
const context = new Context(request, new Container());
|
|
162
|
+
|
|
163
|
+
// Should attempt to serve from /tmp/file.txt
|
|
164
|
+
const response = await middleware(context, async () => new Response('fallback'));
|
|
165
|
+
|
|
166
|
+
expect(response).toBeDefined();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('should skip non-GET requests', async () => {
|
|
170
|
+
const middleware = createStaticFileMiddleware({
|
|
171
|
+
root: '/tmp',
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const request = new Request('http://localhost/file.txt', { method: 'POST' });
|
|
175
|
+
const context = new Context(request, new Container());
|
|
176
|
+
|
|
177
|
+
let nextCalled = false;
|
|
178
|
+
await middleware(context, async () => {
|
|
179
|
+
nextCalled = true;
|
|
180
|
+
return new Response('ok');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(nextCalled).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('File Upload Middleware', () => {
|
|
188
|
+
test('should skip non-POST/PUT requests', async () => {
|
|
189
|
+
const middleware = createFileUploadMiddleware();
|
|
190
|
+
|
|
191
|
+
const request = new Request('http://localhost/upload', { method: 'GET' });
|
|
192
|
+
const context = new Context(request, new Container());
|
|
193
|
+
|
|
194
|
+
let nextCalled = false;
|
|
195
|
+
await middleware(context, async () => {
|
|
196
|
+
nextCalled = true;
|
|
197
|
+
return new Response('ok');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
expect(nextCalled).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('should skip requests without multipart content type', async () => {
|
|
204
|
+
const middleware = createFileUploadMiddleware();
|
|
205
|
+
|
|
206
|
+
const request = new Request('http://localhost/upload', {
|
|
207
|
+
method: 'POST',
|
|
208
|
+
headers: { 'Content-Type': 'application/json' },
|
|
209
|
+
body: JSON.stringify({ data: 'test' }),
|
|
210
|
+
});
|
|
211
|
+
const context = new Context(request, new Container());
|
|
212
|
+
|
|
213
|
+
let nextCalled = false;
|
|
214
|
+
await middleware(context, async () => {
|
|
215
|
+
nextCalled = true;
|
|
216
|
+
return new Response('ok');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
expect(nextCalled).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('should use custom max file size', () => {
|
|
223
|
+
const middleware = createFileUploadMiddleware({
|
|
224
|
+
maxFileSize: 1024 * 1024 * 10, // 10MB
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
expect(middleware).toBeDefined();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('should use custom upload directory', () => {
|
|
231
|
+
const middleware = createFileUploadMiddleware({
|
|
232
|
+
uploadDir: '/tmp/uploads',
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
expect(middleware).toBeDefined();
|
|
236
|
+
});
|
|
237
|
+
});
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach } from 'bun:test';
|
|
2
|
+
import 'reflect-metadata';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
MemoryRateLimitStore,
|
|
6
|
+
createRateLimitMiddleware,
|
|
7
|
+
type RateLimitOptions,
|
|
8
|
+
} from '../../../src/middleware/builtin/rate-limit';
|
|
9
|
+
import { Context } from '../../../src/core/context';
|
|
10
|
+
import { Container } from '../../../src/di/container';
|
|
11
|
+
|
|
12
|
+
describe('MemoryRateLimitStore', () => {
|
|
13
|
+
let store: MemoryRateLimitStore;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
store = new MemoryRateLimitStore();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('get', () => {
|
|
20
|
+
test('should return 0 for non-existent key', async () => {
|
|
21
|
+
const count = await store.get('non-existent');
|
|
22
|
+
expect(count).toBe(0);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('should return current count', async () => {
|
|
26
|
+
await store.increment('key1', 60000);
|
|
27
|
+
await store.increment('key1', 60000);
|
|
28
|
+
const count = await store.get('key1');
|
|
29
|
+
expect(count).toBe(2);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('should return 0 for expired key', async () => {
|
|
33
|
+
await store.increment('key2', 50); // 50ms window
|
|
34
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
35
|
+
const count = await store.get('key2');
|
|
36
|
+
expect(count).toBe(0);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('increment', () => {
|
|
41
|
+
test('should start at 1 for new key', async () => {
|
|
42
|
+
const count = await store.increment('new-key', 60000);
|
|
43
|
+
expect(count).toBe(1);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('should increment existing key', async () => {
|
|
47
|
+
await store.increment('incr-key', 60000);
|
|
48
|
+
await store.increment('incr-key', 60000);
|
|
49
|
+
const count = await store.increment('incr-key', 60000);
|
|
50
|
+
expect(count).toBe(3);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('should reset count after window expires', async () => {
|
|
54
|
+
await store.increment('expire-key', 50);
|
|
55
|
+
await store.increment('expire-key', 50);
|
|
56
|
+
expect(await store.get('expire-key')).toBe(2);
|
|
57
|
+
|
|
58
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
59
|
+
|
|
60
|
+
const count = await store.increment('expire-key', 50);
|
|
61
|
+
expect(count).toBe(1); // Should reset
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('reset', () => {
|
|
66
|
+
test('should reset count to 0', async () => {
|
|
67
|
+
await store.increment('reset-key', 60000);
|
|
68
|
+
await store.increment('reset-key', 60000);
|
|
69
|
+
await store.reset('reset-key');
|
|
70
|
+
const count = await store.get('reset-key');
|
|
71
|
+
expect(count).toBe(0);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('cleanup', () => {
|
|
76
|
+
test('should remove expired entries', async () => {
|
|
77
|
+
await store.increment('cleanup1', 50);
|
|
78
|
+
await store.increment('cleanup2', 60000);
|
|
79
|
+
|
|
80
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
81
|
+
|
|
82
|
+
store.cleanup();
|
|
83
|
+
|
|
84
|
+
expect(await store.get('cleanup1')).toBe(0); // expired
|
|
85
|
+
expect(await store.get('cleanup2')).toBe(1); // still valid
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('createRateLimitMiddleware', () => {
|
|
91
|
+
let container: Container;
|
|
92
|
+
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
container = new Container();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('should allow requests within limit', async () => {
|
|
98
|
+
const middleware = createRateLimitMiddleware({
|
|
99
|
+
max: 5,
|
|
100
|
+
windowMs: 60000,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const request = new Request('http://localhost/test');
|
|
104
|
+
const context = new Context(request, container);
|
|
105
|
+
|
|
106
|
+
let passed = false;
|
|
107
|
+
const next = async (): Promise<Response> => {
|
|
108
|
+
passed = true;
|
|
109
|
+
return new Response('OK');
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
await middleware(context, next);
|
|
113
|
+
expect(passed).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('should block requests exceeding limit', async () => {
|
|
117
|
+
const store = new MemoryRateLimitStore();
|
|
118
|
+
const middleware = createRateLimitMiddleware({
|
|
119
|
+
max: 2,
|
|
120
|
+
windowMs: 60000,
|
|
121
|
+
store,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const request = new Request('http://localhost/test');
|
|
125
|
+
|
|
126
|
+
// First request
|
|
127
|
+
const context1 = new Context(request, container);
|
|
128
|
+
await middleware(context1, async () => new Response('OK'));
|
|
129
|
+
|
|
130
|
+
// Second request
|
|
131
|
+
const context2 = new Context(request, container);
|
|
132
|
+
await middleware(context2, async () => new Response('OK'));
|
|
133
|
+
|
|
134
|
+
// Third request should be blocked
|
|
135
|
+
const context3 = new Context(request, container);
|
|
136
|
+
let blocked = false;
|
|
137
|
+
const response = await middleware(context3, async () => {
|
|
138
|
+
blocked = true;
|
|
139
|
+
return new Response('OK');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(blocked).toBe(false);
|
|
143
|
+
expect(response?.status).toBe(429);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('should use custom key generator', async () => {
|
|
147
|
+
const keys: string[] = [];
|
|
148
|
+
const middleware = createRateLimitMiddleware({
|
|
149
|
+
max: 10,
|
|
150
|
+
windowMs: 60000,
|
|
151
|
+
keyGenerator: (ctx: Context) => {
|
|
152
|
+
const key = ctx.request.headers.get('X-API-Key') ?? 'anonymous';
|
|
153
|
+
keys.push(key);
|
|
154
|
+
return key;
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const request1 = new Request('http://localhost/test', {
|
|
159
|
+
headers: { 'X-API-Key': 'key-abc' },
|
|
160
|
+
});
|
|
161
|
+
const context1 = new Context(request1, container);
|
|
162
|
+
await middleware(context1, async () => new Response('OK'));
|
|
163
|
+
|
|
164
|
+
const request2 = new Request('http://localhost/test', {
|
|
165
|
+
headers: { 'X-API-Key': 'key-xyz' },
|
|
166
|
+
});
|
|
167
|
+
const context2 = new Context(request2, container);
|
|
168
|
+
await middleware(context2, async () => new Response('OK'));
|
|
169
|
+
|
|
170
|
+
expect(keys).toContain('key-abc');
|
|
171
|
+
expect(keys).toContain('key-xyz');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('should track rate limit in context', async () => {
|
|
175
|
+
const middleware = createRateLimitMiddleware({
|
|
176
|
+
max: 5,
|
|
177
|
+
windowMs: 60000,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const request = new Request('http://localhost/test');
|
|
181
|
+
const context = new Context(request, container);
|
|
182
|
+
|
|
183
|
+
await middleware(context, async () => new Response('OK'));
|
|
184
|
+
|
|
185
|
+
// Headers are set on context, not on response
|
|
186
|
+
expect(context.getHeader('X-RateLimit-Limit')).toBeNull(); // getHeader reads request headers, returns null for non-existent
|
|
187
|
+
// The middleware sets response headers via context.setHeader
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('should return 429 response when rate limited', async () => {
|
|
191
|
+
const store = new MemoryRateLimitStore();
|
|
192
|
+
const middleware = createRateLimitMiddleware({
|
|
193
|
+
max: 1,
|
|
194
|
+
windowMs: 60000,
|
|
195
|
+
store,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const request = new Request('http://localhost/test');
|
|
199
|
+
|
|
200
|
+
// First request
|
|
201
|
+
const context1 = new Context(request, container);
|
|
202
|
+
await middleware(context1, async () => new Response('OK'));
|
|
203
|
+
|
|
204
|
+
// Second request should be rate limited
|
|
205
|
+
const context2 = new Context(request, container);
|
|
206
|
+
const response = await middleware(context2, async () => new Response('OK'));
|
|
207
|
+
|
|
208
|
+
expect(response?.status).toBe(429);
|
|
209
|
+
const body = await response?.json() as { error: string };
|
|
210
|
+
expect(body.error).toBe('Too Many Requests');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test('should use default windowMs of 60000', async () => {
|
|
214
|
+
const middleware = createRateLimitMiddleware({
|
|
215
|
+
max: 100,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Should not throw and should create middleware successfully
|
|
219
|
+
expect(typeof middleware).toBe('function');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('should skip rate limiting when skip function returns true', async () => {
|
|
223
|
+
const store = new MemoryRateLimitStore();
|
|
224
|
+
const middleware = createRateLimitMiddleware({
|
|
225
|
+
max: 1,
|
|
226
|
+
windowMs: 60000,
|
|
227
|
+
store,
|
|
228
|
+
skip: (ctx: Context) => ctx.request.url.includes('/health'),
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Health check should be skipped
|
|
232
|
+
const healthRequest = new Request('http://localhost/health');
|
|
233
|
+
const healthContext = new Context(healthRequest, container);
|
|
234
|
+
let healthPassed = false;
|
|
235
|
+
await middleware(healthContext, async () => {
|
|
236
|
+
healthPassed = true;
|
|
237
|
+
return new Response('OK');
|
|
238
|
+
});
|
|
239
|
+
expect(healthPassed).toBe(true);
|
|
240
|
+
|
|
241
|
+
// Normal requests should be rate limited
|
|
242
|
+
const normalRequest1 = new Request('http://localhost/api');
|
|
243
|
+
const normalContext1 = new Context(normalRequest1, container);
|
|
244
|
+
await middleware(normalContext1, async () => new Response('OK'));
|
|
245
|
+
|
|
246
|
+
const normalRequest2 = new Request('http://localhost/api');
|
|
247
|
+
const normalContext2 = new Context(normalRequest2, container);
|
|
248
|
+
let normalPassed = false;
|
|
249
|
+
const response = await middleware(normalContext2, async () => {
|
|
250
|
+
normalPassed = true;
|
|
251
|
+
return new Response('OK');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
expect(normalPassed).toBe(false);
|
|
255
|
+
expect(response?.status).toBe(429);
|
|
256
|
+
});
|
|
257
|
+
});
|