@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,291 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { BodyParser } from '../../src/request/body-parser';
|
|
4
|
+
|
|
5
|
+
describe('BodyParser', () => {
|
|
6
|
+
describe('parse', () => {
|
|
7
|
+
test('should return undefined for GET request', async () => {
|
|
8
|
+
const request = new Request('http://localhost/test', {
|
|
9
|
+
method: 'GET',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const result = await BodyParser.parse(request);
|
|
13
|
+
expect(result).toBeUndefined();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('should return undefined for HEAD request', async () => {
|
|
17
|
+
const request = new Request('http://localhost/test', {
|
|
18
|
+
method: 'HEAD',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const result = await BodyParser.parse(request);
|
|
22
|
+
expect(result).toBeUndefined();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('should return undefined when Content-Length is 0', async () => {
|
|
26
|
+
const request = new Request('http://localhost/test', {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: {
|
|
29
|
+
'Content-Length': '0',
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const result = await BodyParser.parse(request);
|
|
34
|
+
expect(result).toBeUndefined();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('should parse JSON body', async () => {
|
|
38
|
+
const body = { name: 'test', value: 123 };
|
|
39
|
+
const request = new Request('http://localhost/test', {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: {
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
},
|
|
44
|
+
body: JSON.stringify(body),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const result = await BodyParser.parse(request);
|
|
48
|
+
expect(result).toEqual(body);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('should parse JSON with charset', async () => {
|
|
52
|
+
const body = { message: 'hello' };
|
|
53
|
+
const request = new Request('http://localhost/test', {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: {
|
|
56
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify(body),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const result = await BodyParser.parse(request);
|
|
62
|
+
expect(result).toEqual(body);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('should return undefined for empty JSON body', async () => {
|
|
66
|
+
const request = new Request('http://localhost/test', {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: {
|
|
69
|
+
'Content-Type': 'application/json',
|
|
70
|
+
},
|
|
71
|
+
body: '',
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const result = await BodyParser.parse(request);
|
|
75
|
+
expect(result).toBeUndefined();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('should parse FormData', async () => {
|
|
79
|
+
const formData = new FormData();
|
|
80
|
+
formData.append('name', 'test');
|
|
81
|
+
formData.append('file', new Blob(['content']), 'test.txt');
|
|
82
|
+
|
|
83
|
+
const request = new Request('http://localhost/test', {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
body: formData,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const result = await BodyParser.parse(request);
|
|
89
|
+
expect(result).toBeInstanceOf(FormData);
|
|
90
|
+
expect((result as FormData).get('name')).toBe('test');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('should parse URL-encoded body', async () => {
|
|
94
|
+
const request = new Request('http://localhost/test', {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
headers: {
|
|
97
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
98
|
+
},
|
|
99
|
+
body: 'name=test&age=25',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const result = await BodyParser.parse(request);
|
|
103
|
+
expect(result).toEqual({ name: 'test', age: '25' });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('should return undefined for empty URL-encoded body', async () => {
|
|
107
|
+
const request = new Request('http://localhost/test', {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers: {
|
|
110
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
111
|
+
},
|
|
112
|
+
body: '',
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const result = await BodyParser.parse(request);
|
|
116
|
+
expect(result).toBeUndefined();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('should parse text/plain body', async () => {
|
|
120
|
+
const request = new Request('http://localhost/test', {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: {
|
|
123
|
+
'Content-Type': 'text/plain',
|
|
124
|
+
},
|
|
125
|
+
body: 'Hello, World!',
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const result = await BodyParser.parse(request);
|
|
129
|
+
expect(result).toBe('Hello, World!');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('should parse text/html body', async () => {
|
|
133
|
+
const html = '<html><body>Test</body></html>';
|
|
134
|
+
const request = new Request('http://localhost/test', {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
headers: {
|
|
137
|
+
'Content-Type': 'text/html',
|
|
138
|
+
},
|
|
139
|
+
body: html,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const result = await BodyParser.parse(request);
|
|
143
|
+
expect(result).toBe(html);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('should try JSON for unknown content type with JSON body', async () => {
|
|
147
|
+
const body = { key: 'value' };
|
|
148
|
+
const request = new Request('http://localhost/test', {
|
|
149
|
+
method: 'POST',
|
|
150
|
+
headers: {
|
|
151
|
+
'Content-Type': 'application/octet-stream',
|
|
152
|
+
'Content-Length': String(JSON.stringify(body).length),
|
|
153
|
+
},
|
|
154
|
+
body: JSON.stringify(body),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const result = await BodyParser.parse(request);
|
|
158
|
+
expect(result).toEqual(body);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('should return text for unknown content type with non-JSON body', async () => {
|
|
162
|
+
const text = 'not a json';
|
|
163
|
+
const request = new Request('http://localhost/test', {
|
|
164
|
+
method: 'POST',
|
|
165
|
+
headers: {
|
|
166
|
+
'Content-Type': 'application/octet-stream',
|
|
167
|
+
'Content-Length': String(text.length),
|
|
168
|
+
},
|
|
169
|
+
body: text,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const result = await BodyParser.parse(request);
|
|
173
|
+
expect(result).toBe(text);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('should handle body without Content-Type', async () => {
|
|
177
|
+
const body = { data: 'test' };
|
|
178
|
+
const request = new Request('http://localhost/test', {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
body: JSON.stringify(body),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const result = await BodyParser.parse(request);
|
|
184
|
+
// 应该尝试解析为 JSON
|
|
185
|
+
expect(result).toEqual(body);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('should return text when body without Content-Type is not JSON', async () => {
|
|
189
|
+
const text = 'plain text without content type';
|
|
190
|
+
const request = new Request('http://localhost/test', {
|
|
191
|
+
method: 'POST',
|
|
192
|
+
body: text,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const result = await BodyParser.parse(request);
|
|
196
|
+
expect(result).toBe(text);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('should return undefined for empty body without Content-Type', async () => {
|
|
200
|
+
const request = new Request('http://localhost/test', {
|
|
201
|
+
method: 'POST',
|
|
202
|
+
body: '',
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const result = await BodyParser.parse(request);
|
|
206
|
+
expect(result).toBeUndefined();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('should handle complex JSON structures', async () => {
|
|
210
|
+
const body = {
|
|
211
|
+
users: [
|
|
212
|
+
{ id: 1, name: 'Alice' },
|
|
213
|
+
{ id: 2, name: 'Bob' },
|
|
214
|
+
],
|
|
215
|
+
meta: {
|
|
216
|
+
total: 2,
|
|
217
|
+
page: 1,
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const request = new Request('http://localhost/test', {
|
|
222
|
+
method: 'POST',
|
|
223
|
+
headers: {
|
|
224
|
+
'Content-Type': 'application/json',
|
|
225
|
+
},
|
|
226
|
+
body: JSON.stringify(body),
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const result = await BodyParser.parse(request);
|
|
230
|
+
expect(result).toEqual(body);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test('should handle URL-encoded with special characters', async () => {
|
|
234
|
+
const request = new Request('http://localhost/test', {
|
|
235
|
+
method: 'POST',
|
|
236
|
+
headers: {
|
|
237
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
238
|
+
},
|
|
239
|
+
body: 'name=John%20Doe&email=test%40example.com',
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const result = await BodyParser.parse(request);
|
|
243
|
+
expect(result).toEqual({
|
|
244
|
+
name: 'John Doe',
|
|
245
|
+
email: 'test@example.com',
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test('should handle PUT request with body', async () => {
|
|
250
|
+
const body = { updated: true };
|
|
251
|
+
const request = new Request('http://localhost/test', {
|
|
252
|
+
method: 'PUT',
|
|
253
|
+
headers: {
|
|
254
|
+
'Content-Type': 'application/json',
|
|
255
|
+
},
|
|
256
|
+
body: JSON.stringify(body),
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const result = await BodyParser.parse(request);
|
|
260
|
+
expect(result).toEqual(body);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test('should handle DELETE request with body', async () => {
|
|
264
|
+
const body = { ids: [1, 2, 3] };
|
|
265
|
+
const request = new Request('http://localhost/test', {
|
|
266
|
+
method: 'DELETE',
|
|
267
|
+
headers: {
|
|
268
|
+
'Content-Type': 'application/json',
|
|
269
|
+
},
|
|
270
|
+
body: JSON.stringify(body),
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const result = await BodyParser.parse(request);
|
|
274
|
+
expect(result).toEqual(body);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test('should handle PATCH request with body', async () => {
|
|
278
|
+
const body = { field: 'newValue' };
|
|
279
|
+
const request = new Request('http://localhost/test', {
|
|
280
|
+
method: 'PATCH',
|
|
281
|
+
headers: {
|
|
282
|
+
'Content-Type': 'application/json',
|
|
283
|
+
},
|
|
284
|
+
body: JSON.stringify(body),
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const result = await BodyParser.parse(request);
|
|
288
|
+
expect(result).toEqual(body);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
});
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { RequestWrapper } from '../../src/request/request';
|
|
4
|
+
|
|
5
|
+
describe('RequestWrapper', () => {
|
|
6
|
+
describe('constructor', () => {
|
|
7
|
+
test('should create wrapper with basic request properties', () => {
|
|
8
|
+
const request = new Request('http://localhost:3000/api/users?name=test');
|
|
9
|
+
const wrapper = new RequestWrapper(request);
|
|
10
|
+
|
|
11
|
+
expect(wrapper.request).toBe(request);
|
|
12
|
+
expect(wrapper.method).toBe('GET');
|
|
13
|
+
expect(wrapper.path).toBe('/api/users');
|
|
14
|
+
expect(wrapper.url.href).toBe('http://localhost:3000/api/users?name=test');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('should parse URL correctly', () => {
|
|
18
|
+
const request = new Request('http://example.com:8080/path/to/resource');
|
|
19
|
+
const wrapper = new RequestWrapper(request);
|
|
20
|
+
|
|
21
|
+
expect(wrapper.url.hostname).toBe('example.com');
|
|
22
|
+
expect(wrapper.url.port).toBe('8080');
|
|
23
|
+
expect(wrapper.url.pathname).toBe('/path/to/resource');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('should extract path correctly', () => {
|
|
27
|
+
const request = new Request('http://localhost/users/123/posts?page=1');
|
|
28
|
+
const wrapper = new RequestWrapper(request);
|
|
29
|
+
|
|
30
|
+
expect(wrapper.path).toBe('/users/123/posts');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('should extract headers', () => {
|
|
34
|
+
const request = new Request('http://localhost/test', {
|
|
35
|
+
headers: {
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
'Authorization': 'Bearer token123',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const wrapper = new RequestWrapper(request);
|
|
41
|
+
|
|
42
|
+
expect(wrapper.headers.get('Content-Type')).toBe('application/json');
|
|
43
|
+
expect(wrapper.headers.get('Authorization')).toBe('Bearer token123');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('should handle different HTTP methods', () => {
|
|
47
|
+
const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'];
|
|
48
|
+
|
|
49
|
+
for (const method of methods) {
|
|
50
|
+
const request = new Request('http://localhost/test', { method });
|
|
51
|
+
const wrapper = new RequestWrapper(request);
|
|
52
|
+
expect(wrapper.method).toBe(method);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('query', () => {
|
|
58
|
+
test('should parse query parameters', () => {
|
|
59
|
+
const request = new Request('http://localhost/search?q=test&page=1&limit=10');
|
|
60
|
+
const wrapper = new RequestWrapper(request);
|
|
61
|
+
|
|
62
|
+
expect(wrapper.query.get('q')).toBe('test');
|
|
63
|
+
expect(wrapper.query.get('page')).toBe('1');
|
|
64
|
+
expect(wrapper.query.get('limit')).toBe('10');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('should return null for missing query parameter', () => {
|
|
68
|
+
const request = new Request('http://localhost/search?q=test');
|
|
69
|
+
const wrapper = new RequestWrapper(request);
|
|
70
|
+
|
|
71
|
+
expect(wrapper.query.get('missing')).toBeNull();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('should handle URL without query parameters', () => {
|
|
75
|
+
const request = new Request('http://localhost/api');
|
|
76
|
+
const wrapper = new RequestWrapper(request);
|
|
77
|
+
|
|
78
|
+
expect(wrapper.query.toString()).toBe('');
|
|
79
|
+
expect(wrapper.query.get('any')).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('should handle URL-encoded query parameters', () => {
|
|
83
|
+
const request = new Request('http://localhost/search?name=John%20Doe&email=test%40example.com');
|
|
84
|
+
const wrapper = new RequestWrapper(request);
|
|
85
|
+
|
|
86
|
+
expect(wrapper.query.get('name')).toBe('John Doe');
|
|
87
|
+
expect(wrapper.query.get('email')).toBe('test@example.com');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('getQuery', () => {
|
|
92
|
+
test('should return query parameter value', () => {
|
|
93
|
+
const request = new Request('http://localhost/test?foo=bar');
|
|
94
|
+
const wrapper = new RequestWrapper(request);
|
|
95
|
+
|
|
96
|
+
expect(wrapper.getQuery('foo')).toBe('bar');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('should return null for missing parameter', () => {
|
|
100
|
+
const request = new Request('http://localhost/test');
|
|
101
|
+
const wrapper = new RequestWrapper(request);
|
|
102
|
+
|
|
103
|
+
expect(wrapper.getQuery('foo')).toBeNull();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('getQueryAll', () => {
|
|
108
|
+
test('should return all query parameters as object', () => {
|
|
109
|
+
const request = new Request('http://localhost/test?a=1&b=2&c=3');
|
|
110
|
+
const wrapper = new RequestWrapper(request);
|
|
111
|
+
|
|
112
|
+
const queryAll = wrapper.getQueryAll();
|
|
113
|
+
|
|
114
|
+
expect(queryAll).toEqual({
|
|
115
|
+
a: '1',
|
|
116
|
+
b: '2',
|
|
117
|
+
c: '3',
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('should return empty object when no query parameters', () => {
|
|
122
|
+
const request = new Request('http://localhost/test');
|
|
123
|
+
const wrapper = new RequestWrapper(request);
|
|
124
|
+
|
|
125
|
+
const queryAll = wrapper.getQueryAll();
|
|
126
|
+
|
|
127
|
+
expect(queryAll).toEqual({});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('should handle duplicate keys (last value wins)', () => {
|
|
131
|
+
// URLSearchParams 的 get 方法返回第一个值,但 forEach 会遍历所有
|
|
132
|
+
// 我们的实现会用后面的值覆盖前面的
|
|
133
|
+
const request = new Request('http://localhost/test?key=value1&key=value2');
|
|
134
|
+
const wrapper = new RequestWrapper(request);
|
|
135
|
+
|
|
136
|
+
const queryAll = wrapper.getQueryAll();
|
|
137
|
+
|
|
138
|
+
// 后面的值会覆盖前面的
|
|
139
|
+
expect(queryAll.key).toBe('value2');
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('getHeader', () => {
|
|
144
|
+
test('should return header value', () => {
|
|
145
|
+
const request = new Request('http://localhost/test', {
|
|
146
|
+
headers: {
|
|
147
|
+
'X-Custom-Header': 'custom-value',
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
const wrapper = new RequestWrapper(request);
|
|
151
|
+
|
|
152
|
+
expect(wrapper.getHeader('X-Custom-Header')).toBe('custom-value');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('should return null for missing header', () => {
|
|
156
|
+
const request = new Request('http://localhost/test');
|
|
157
|
+
const wrapper = new RequestWrapper(request);
|
|
158
|
+
|
|
159
|
+
expect(wrapper.getHeader('X-Missing-Header')).toBeNull();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('should be case-insensitive', () => {
|
|
163
|
+
const request = new Request('http://localhost/test', {
|
|
164
|
+
headers: {
|
|
165
|
+
'Content-Type': 'application/json',
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
const wrapper = new RequestWrapper(request);
|
|
169
|
+
|
|
170
|
+
expect(wrapper.getHeader('content-type')).toBe('application/json');
|
|
171
|
+
expect(wrapper.getHeader('CONTENT-TYPE')).toBe('application/json');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('body', () => {
|
|
176
|
+
test('should parse JSON body', async () => {
|
|
177
|
+
const request = new Request('http://localhost/test', {
|
|
178
|
+
method: 'POST',
|
|
179
|
+
headers: {
|
|
180
|
+
'Content-Type': 'application/json',
|
|
181
|
+
},
|
|
182
|
+
body: JSON.stringify({ name: 'test', value: 123 }),
|
|
183
|
+
});
|
|
184
|
+
const wrapper = new RequestWrapper(request);
|
|
185
|
+
|
|
186
|
+
const body = await wrapper.body();
|
|
187
|
+
|
|
188
|
+
expect(body).toEqual({ name: 'test', value: 123 });
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('should cache parsed body', async () => {
|
|
192
|
+
const request = new Request('http://localhost/test', {
|
|
193
|
+
method: 'POST',
|
|
194
|
+
headers: {
|
|
195
|
+
'Content-Type': 'application/json',
|
|
196
|
+
},
|
|
197
|
+
body: JSON.stringify({ cached: true }),
|
|
198
|
+
});
|
|
199
|
+
const wrapper = new RequestWrapper(request);
|
|
200
|
+
|
|
201
|
+
const body1 = await wrapper.body();
|
|
202
|
+
const body2 = await wrapper.body();
|
|
203
|
+
|
|
204
|
+
expect(body1).toBe(body2); // 同一个引用
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('should parse form data', async () => {
|
|
208
|
+
const formData = new FormData();
|
|
209
|
+
formData.append('username', 'testuser');
|
|
210
|
+
formData.append('password', 'secret');
|
|
211
|
+
|
|
212
|
+
const request = new Request('http://localhost/test', {
|
|
213
|
+
method: 'POST',
|
|
214
|
+
body: formData,
|
|
215
|
+
});
|
|
216
|
+
const wrapper = new RequestWrapper(request);
|
|
217
|
+
|
|
218
|
+
const body = await wrapper.body();
|
|
219
|
+
|
|
220
|
+
expect(body).toBeInstanceOf(FormData);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('should parse URL-encoded body', async () => {
|
|
224
|
+
const request = new Request('http://localhost/test', {
|
|
225
|
+
method: 'POST',
|
|
226
|
+
headers: {
|
|
227
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
228
|
+
},
|
|
229
|
+
body: 'name=test&value=123',
|
|
230
|
+
});
|
|
231
|
+
const wrapper = new RequestWrapper(request);
|
|
232
|
+
|
|
233
|
+
const body = await wrapper.body();
|
|
234
|
+
|
|
235
|
+
expect(body).toEqual({ name: 'test', value: '123' });
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('should handle empty body', async () => {
|
|
239
|
+
const request = new Request('http://localhost/test', {
|
|
240
|
+
method: 'POST',
|
|
241
|
+
headers: {
|
|
242
|
+
'Content-Type': 'application/json',
|
|
243
|
+
},
|
|
244
|
+
body: '',
|
|
245
|
+
});
|
|
246
|
+
const wrapper = new RequestWrapper(request);
|
|
247
|
+
|
|
248
|
+
const body = await wrapper.body();
|
|
249
|
+
|
|
250
|
+
// 空字符串 JSON 解析会返回 null 或抛出错误
|
|
251
|
+
// BodyParser 对空体应该返回 null
|
|
252
|
+
expect(body === null || body === undefined || body === '').toBe(true);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe('getBody', () => {
|
|
257
|
+
test('should return undefined before body is parsed', () => {
|
|
258
|
+
const request = new Request('http://localhost/test', {
|
|
259
|
+
method: 'POST',
|
|
260
|
+
headers: {
|
|
261
|
+
'Content-Type': 'application/json',
|
|
262
|
+
},
|
|
263
|
+
body: JSON.stringify({ test: true }),
|
|
264
|
+
});
|
|
265
|
+
const wrapper = new RequestWrapper(request);
|
|
266
|
+
|
|
267
|
+
expect(wrapper.getBody()).toBeUndefined();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test('should return parsed body after body() is called', async () => {
|
|
271
|
+
const request = new Request('http://localhost/test', {
|
|
272
|
+
method: 'POST',
|
|
273
|
+
headers: {
|
|
274
|
+
'Content-Type': 'application/json',
|
|
275
|
+
},
|
|
276
|
+
body: JSON.stringify({ test: true }),
|
|
277
|
+
});
|
|
278
|
+
const wrapper = new RequestWrapper(request);
|
|
279
|
+
|
|
280
|
+
// 先解析 body
|
|
281
|
+
await wrapper.body();
|
|
282
|
+
|
|
283
|
+
// 现在 getBody 应该返回已解析的值
|
|
284
|
+
expect(wrapper.getBody()).toEqual({ test: true });
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe('edge cases', () => {
|
|
289
|
+
test('should handle root path', () => {
|
|
290
|
+
const request = new Request('http://localhost/');
|
|
291
|
+
const wrapper = new RequestWrapper(request);
|
|
292
|
+
|
|
293
|
+
expect(wrapper.path).toBe('/');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test('should handle path without leading slash (edge case)', () => {
|
|
297
|
+
// URL 规范会自动添加 /
|
|
298
|
+
const request = new Request('http://localhost');
|
|
299
|
+
const wrapper = new RequestWrapper(request);
|
|
300
|
+
|
|
301
|
+
expect(wrapper.path).toBe('/');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('should handle complex query strings', () => {
|
|
305
|
+
const request = new Request('http://localhost/api?arr[]=1&arr[]=2&obj[key]=value');
|
|
306
|
+
const wrapper = new RequestWrapper(request);
|
|
307
|
+
|
|
308
|
+
expect(wrapper.query.getAll('arr[]')).toEqual(['1', '2']);
|
|
309
|
+
expect(wrapper.query.get('obj[key]')).toBe('value');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test('should handle special characters in path', () => {
|
|
313
|
+
const request = new Request('http://localhost/api/users/user%40example.com');
|
|
314
|
+
const wrapper = new RequestWrapper(request);
|
|
315
|
+
|
|
316
|
+
expect(wrapper.path).toBe('/api/users/user%40example.com');
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
});
|