@dangao/bun-server 1.8.0 → 1.8.2

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.
Files changed (62) hide show
  1. package/docs/api.md +194 -81
  2. package/docs/extensions.md +53 -0
  3. package/docs/guide.md +243 -1
  4. package/docs/microservice-config-center.md +73 -74
  5. package/docs/microservice-nacos.md +89 -90
  6. package/docs/microservice-service-registry.md +85 -86
  7. package/docs/microservice.md +142 -137
  8. package/docs/request-lifecycle.md +45 -4
  9. package/docs/symbol-interface-pattern.md +106 -106
  10. package/docs/zh/api.md +458 -18
  11. package/docs/zh/extensions.md +53 -0
  12. package/docs/zh/guide.md +251 -4
  13. package/docs/zh/microservice-config-center.md +258 -0
  14. package/docs/zh/microservice-nacos.md +346 -0
  15. package/docs/zh/microservice-service-registry.md +306 -0
  16. package/docs/zh/microservice.md +680 -0
  17. package/docs/zh/request-lifecycle.md +43 -5
  18. package/package.json +1 -1
  19. package/tests/auth/auth-decorators.test.ts +241 -0
  20. package/tests/auth/oauth2-service.test.ts +318 -0
  21. package/tests/cache/cache-decorators-extended.test.ts +272 -0
  22. package/tests/cache/cache-interceptors.test.ts +534 -0
  23. package/tests/cache/cache-service-proxy.test.ts +246 -0
  24. package/tests/cache/memory-cache-store.test.ts +155 -0
  25. package/tests/cache/redis-cache-store.test.ts +199 -0
  26. package/tests/config/config-center-integration.test.ts +334 -0
  27. package/tests/config/config-module-extended.test.ts +165 -0
  28. package/tests/controller/param-binder.test.ts +333 -0
  29. package/tests/error/error-handler.test.ts +166 -57
  30. package/tests/error/i18n-extended.test.ts +105 -0
  31. package/tests/events/event-listener-scanner.test.ts +114 -0
  32. package/tests/events/event-module.test.ts +133 -302
  33. package/tests/extensions/logger-module.test.ts +158 -0
  34. package/tests/files/file-storage.test.ts +136 -0
  35. package/tests/interceptor/base-interceptor.test.ts +605 -0
  36. package/tests/interceptor/builtin/cache-interceptor.test.ts +233 -86
  37. package/tests/interceptor/builtin/log-interceptor.test.ts +469 -0
  38. package/tests/interceptor/builtin/permission-interceptor.test.ts +219 -120
  39. package/tests/interceptor/interceptor-chain.test.ts +241 -189
  40. package/tests/interceptor/interceptor-metadata.test.ts +221 -0
  41. package/tests/microservice/circuit-breaker.test.ts +221 -0
  42. package/tests/microservice/service-client-decorators.test.ts +86 -0
  43. package/tests/microservice/service-client-interceptors.test.ts +274 -0
  44. package/tests/microservice/service-registry-decorators.test.ts +147 -0
  45. package/tests/microservice/tracer.test.ts +213 -0
  46. package/tests/microservice/tracing-collectors.test.ts +168 -0
  47. package/tests/middleware/builtin/middleware-builtin-extended.test.ts +237 -0
  48. package/tests/middleware/builtin/rate-limit.test.ts +257 -0
  49. package/tests/middleware/middleware-decorators.test.ts +222 -0
  50. package/tests/middleware/middleware-pipeline.test.ts +160 -0
  51. package/tests/queue/queue-decorators.test.ts +139 -0
  52. package/tests/queue/queue-service.test.ts +191 -0
  53. package/tests/request/body-parser-extended.test.ts +291 -0
  54. package/tests/request/request-wrapper.test.ts +319 -0
  55. package/tests/router/router-decorators.test.ts +260 -0
  56. package/tests/router/router-extended.test.ts +298 -0
  57. package/tests/security/guards/reflector.test.ts +188 -0
  58. package/tests/security/security-filter.test.ts +182 -0
  59. package/tests/security/security-module-extended.test.ts +133 -0
  60. package/tests/session/memory-session-store.test.ts +172 -0
  61. package/tests/session/session-decorators.test.ts +163 -0
  62. package/tests/swagger/ui.test.ts +212 -0
@@ -0,0 +1,333 @@
1
+ import { describe, expect, test, beforeEach } from 'bun:test';
2
+ import 'reflect-metadata';
3
+
4
+ import { ParamBinder } from '../../src/controller/param-binder';
5
+ import {
6
+ Body,
7
+ Query,
8
+ Param,
9
+ Header,
10
+ Context as ContextDecorator,
11
+ QueryMap,
12
+ HeaderMap,
13
+ } from '../../src/controller/decorators';
14
+ import { Context } from '../../src/core/context';
15
+ import { Container } from '../../src/di/container';
16
+
17
+ describe('ParamBinder', () => {
18
+ let container: Container;
19
+
20
+ beforeEach(() => {
21
+ container = new Container();
22
+ });
23
+
24
+ describe('bind', () => {
25
+ test('should bind @Body parameter', async () => {
26
+ class TestController {
27
+ public testMethod(@Body() body: unknown): void {}
28
+ }
29
+
30
+ const request = new Request('http://localhost/test', {
31
+ method: 'POST',
32
+ headers: { 'Content-Type': 'application/json' },
33
+ body: JSON.stringify({ name: 'test' }),
34
+ });
35
+ const context = new Context(request, container);
36
+
37
+ const params = await ParamBinder.bind(
38
+ TestController.prototype,
39
+ 'testMethod',
40
+ context,
41
+ container,
42
+ );
43
+
44
+ expect(params[0]).toEqual({ name: 'test' });
45
+ });
46
+
47
+ test('should bind @Body with key parameter', async () => {
48
+ class TestController {
49
+ public testMethod(@Body('name') name: string): void {}
50
+ }
51
+
52
+ const request = new Request('http://localhost/test', {
53
+ method: 'POST',
54
+ headers: { 'Content-Type': 'application/json' },
55
+ body: JSON.stringify({ name: 'alice', age: 25 }),
56
+ });
57
+ const context = new Context(request, container);
58
+
59
+ const params = await ParamBinder.bind(
60
+ TestController.prototype,
61
+ 'testMethod',
62
+ context,
63
+ container,
64
+ );
65
+
66
+ expect(params[0]).toBe('alice');
67
+ });
68
+
69
+ test('should bind @Query parameter', async () => {
70
+ class TestController {
71
+ public testMethod(@Query('id') id: string): void {}
72
+ }
73
+
74
+ const request = new Request('http://localhost/test?id=123');
75
+ const context = new Context(request, container);
76
+
77
+ const params = await ParamBinder.bind(
78
+ TestController.prototype,
79
+ 'testMethod',
80
+ context,
81
+ container,
82
+ );
83
+
84
+ expect(params[0]).toBe('123');
85
+ });
86
+
87
+ test('should bind @Param parameter', async () => {
88
+ class TestController {
89
+ public testMethod(@Param('id') id: string): void {}
90
+ }
91
+
92
+ const request = new Request('http://localhost/users/456');
93
+ const context = new Context(request, container);
94
+ context.params = { id: '456' };
95
+
96
+ const params = await ParamBinder.bind(
97
+ TestController.prototype,
98
+ 'testMethod',
99
+ context,
100
+ container,
101
+ );
102
+
103
+ expect(params[0]).toBe('456');
104
+ });
105
+
106
+ test('should bind @Header parameter', async () => {
107
+ class TestController {
108
+ public testMethod(@Header('x-custom') custom: string): void {}
109
+ }
110
+
111
+ const request = new Request('http://localhost/test', {
112
+ headers: { 'X-Custom': 'custom-value' },
113
+ });
114
+ const context = new Context(request, container);
115
+
116
+ const params = await ParamBinder.bind(
117
+ TestController.prototype,
118
+ 'testMethod',
119
+ context,
120
+ container,
121
+ );
122
+
123
+ expect(params[0]).toBe('custom-value');
124
+ });
125
+
126
+ test('should bind @Context parameter', async () => {
127
+ class TestController {
128
+ public testMethod(@ContextDecorator() ctx: Context): void {}
129
+ }
130
+
131
+ const request = new Request('http://localhost/test');
132
+ const context = new Context(request, container);
133
+
134
+ const params = await ParamBinder.bind(
135
+ TestController.prototype,
136
+ 'testMethod',
137
+ context,
138
+ container,
139
+ );
140
+
141
+ // 返回的是 context 或 AsyncLocalStorage 中的 context
142
+ expect(params[0]).toBeDefined();
143
+ });
144
+
145
+ test('should bind multiple parameters in correct order', async () => {
146
+ class TestController {
147
+ public testMethod(
148
+ @Query('name') name: string,
149
+ @Query('age') age: string,
150
+ @Header('x-token') token: string,
151
+ ): void {}
152
+ }
153
+
154
+ const request = new Request('http://localhost/test?name=alice&age=25', {
155
+ headers: { 'X-Token': 'abc123' },
156
+ });
157
+ const context = new Context(request, container);
158
+
159
+ const params = await ParamBinder.bind(
160
+ TestController.prototype,
161
+ 'testMethod',
162
+ context,
163
+ container,
164
+ );
165
+
166
+ expect(params[0]).toBe('alice');
167
+ expect(params[1]).toBe('25');
168
+ expect(params[2]).toBe('abc123');
169
+ });
170
+
171
+ test('should return empty array for method without decorators', async () => {
172
+ class TestController {
173
+ public testMethod(arg: string): void {}
174
+ }
175
+
176
+ const request = new Request('http://localhost/test');
177
+ const context = new Context(request, container);
178
+
179
+ const params = await ParamBinder.bind(
180
+ TestController.prototype,
181
+ 'testMethod',
182
+ context,
183
+ container,
184
+ );
185
+
186
+ expect(params).toEqual([]);
187
+ });
188
+
189
+ test('should handle @QueryMap parameter', async () => {
190
+ class TestController {
191
+ public testMethod(@QueryMap() query: Record<string, string>): void {}
192
+ }
193
+
194
+ const request = new Request('http://localhost/test?foo=bar&baz=qux');
195
+ const context = new Context(request, container);
196
+
197
+ const params = await ParamBinder.bind(
198
+ TestController.prototype,
199
+ 'testMethod',
200
+ context,
201
+ container,
202
+ );
203
+
204
+ expect(params[0]).toEqual({ foo: 'bar', baz: 'qux' });
205
+ });
206
+
207
+ test('should handle @QueryMap with transform', async () => {
208
+ class TestController {
209
+ public testMethod(
210
+ @QueryMap({
211
+ transform: (map) => ({ ...map, transformed: true }),
212
+ })
213
+ query: Record<string, unknown>,
214
+ ): void {}
215
+ }
216
+
217
+ const request = new Request('http://localhost/test?foo=bar');
218
+ const context = new Context(request, container);
219
+
220
+ const params = await ParamBinder.bind(
221
+ TestController.prototype,
222
+ 'testMethod',
223
+ context,
224
+ container,
225
+ );
226
+
227
+ expect((params[0] as any).transformed).toBe(true);
228
+ });
229
+
230
+ test('should handle @HeaderMap parameter', async () => {
231
+ class TestController {
232
+ public testMethod(@HeaderMap() headers: Record<string, string>): void {}
233
+ }
234
+
235
+ const request = new Request('http://localhost/test', {
236
+ headers: { 'X-Custom': 'value1', 'X-Another': 'value2' },
237
+ });
238
+ const context = new Context(request, container);
239
+
240
+ const params = await ParamBinder.bind(
241
+ TestController.prototype,
242
+ 'testMethod',
243
+ context,
244
+ container,
245
+ );
246
+
247
+ expect((params[0] as any)['x-custom']).toBe('value1');
248
+ expect((params[0] as any)['x-another']).toBe('value2');
249
+ });
250
+
251
+ test('should handle @HeaderMap with pick', async () => {
252
+ class TestController {
253
+ public testMethod(
254
+ @HeaderMap({ pick: ['x-custom'] })
255
+ headers: Record<string, string>,
256
+ ): void {}
257
+ }
258
+
259
+ const request = new Request('http://localhost/test', {
260
+ headers: { 'X-Custom': 'value1', 'X-Another': 'value2' },
261
+ });
262
+ const context = new Context(request, container);
263
+
264
+ const params = await ParamBinder.bind(
265
+ TestController.prototype,
266
+ 'testMethod',
267
+ context,
268
+ container,
269
+ );
270
+
271
+ expect((params[0] as any)['x-custom']).toBe('value1');
272
+ expect((params[0] as any)['x-another']).toBeUndefined();
273
+ });
274
+
275
+ test('should handle missing body key', async () => {
276
+ class TestController {
277
+ public testMethod(@Body('missing') value: string): void {}
278
+ }
279
+
280
+ const request = new Request('http://localhost/test', {
281
+ method: 'POST',
282
+ headers: { 'Content-Type': 'application/json' },
283
+ body: JSON.stringify({ name: 'test' }),
284
+ });
285
+ const context = new Context(request, container);
286
+
287
+ const params = await ParamBinder.bind(
288
+ TestController.prototype,
289
+ 'testMethod',
290
+ context,
291
+ container,
292
+ );
293
+
294
+ expect(params[0]).toBeUndefined();
295
+ });
296
+
297
+ test('should return null for missing query param', async () => {
298
+ class TestController {
299
+ public testMethod(@Query('missing') value: string): void {}
300
+ }
301
+
302
+ const request = new Request('http://localhost/test');
303
+ const context = new Context(request, container);
304
+
305
+ const params = await ParamBinder.bind(
306
+ TestController.prototype,
307
+ 'testMethod',
308
+ context,
309
+ container,
310
+ );
311
+
312
+ expect(params[0]).toBeNull();
313
+ });
314
+
315
+ test('should return undefined for missing path param', async () => {
316
+ class TestController {
317
+ public testMethod(@Param('missing') value: string): void {}
318
+ }
319
+
320
+ const request = new Request('http://localhost/test');
321
+ const context = new Context(request, container);
322
+
323
+ const params = await ParamBinder.bind(
324
+ TestController.prototype,
325
+ 'testMethod',
326
+ context,
327
+ container,
328
+ );
329
+
330
+ expect(params[0]).toBeUndefined();
331
+ });
332
+ });
333
+ });
@@ -1,68 +1,177 @@
1
- import { describe, expect, test } from 'bun:test';
1
+ import { describe, expect, test, beforeEach } from 'bun:test';
2
+ import 'reflect-metadata';
2
3
 
3
- import { Context } from '../../src/core/context';
4
4
  import { handleError } from '../../src/error/handler';
5
- import { HttpException, BadRequestException } from '../../src/error/http-exception';
5
+ import { HttpException, BadRequestException, UnauthorizedException, ForbiddenException, NotFoundException } from '../../src/error';
6
6
  import { ValidationError } from '../../src/validation';
7
- import { ExceptionFilterRegistry, type ExceptionFilter } from '../../src/error/filter';
8
-
9
- function createContext(url: string = 'http://localhost/api/error'): Context {
10
- return new Context(new Request(url));
11
- }
12
-
13
- describe('Error Handler', () => {
14
- test('should handle HttpException', async () => {
15
- const ctx = createContext();
16
- const error = new BadRequestException('Invalid payload');
17
- const response = await handleError(error, ctx);
18
- expect(response.status).toBe(400);
19
- const data = await response.json();
20
- expect(data.error).toBe('Invalid payload');
21
- });
7
+ import { Context } from '../../src/core/context';
8
+ import { Container } from '../../src/di/container';
22
9
 
23
- test('should handle ValidationError', async () => {
24
- const ctx = createContext();
25
- const validationError = new ValidationError('Validation failed', [
26
- { index: 0, rule: 'isString', message: 'Must be string' },
27
- ]);
28
- const response = await handleError(validationError, ctx);
29
- expect(response.status).toBe(400);
30
- const data = await response.json();
31
- expect(data.issues.length).toBe(1);
10
+ describe('handleError', () => {
11
+ let container: Container;
12
+
13
+ beforeEach(() => {
14
+ container = new Container();
32
15
  });
33
16
 
34
- test('should allow custom exception filter', async () => {
35
- const registry = ExceptionFilterRegistry.getInstance();
36
- registry.clear();
37
-
38
- const filter: ExceptionFilter = {
39
- catch(error, context) {
40
- if (error instanceof Error && error.message === 'custom') {
41
- context.setStatus(418);
42
- return context.createResponse({ error: 'filtered' });
43
- }
44
- return undefined;
45
- },
46
- };
47
-
48
- registry.register(filter);
49
-
50
- const ctx = createContext();
51
- const response = await handleError(new Error('custom'), ctx);
52
- expect(response.status).toBe(418);
53
- const data = await response.json();
54
- expect(data.error).toBe('filtered');
55
-
56
- registry.clear();
17
+ function createContext(): Context {
18
+ const request = new Request('http://localhost/test');
19
+ return new Context(request, container);
20
+ }
21
+
22
+ describe('HttpException handling', () => {
23
+ test('should handle basic HttpException', async () => {
24
+ const context = createContext();
25
+ const error = new HttpException(400, 'Bad request');
26
+
27
+ const response = await handleError(error, context);
28
+ const body = await response.json() as { error: string };
29
+
30
+ expect(response.status).toBe(400);
31
+ expect(body.error).toBe('Bad request');
32
+ });
33
+
34
+ test('should handle BadRequestException', async () => {
35
+ const context = createContext();
36
+ const error = new BadRequestException('Invalid input');
37
+
38
+ const response = await handleError(error, context);
39
+ const body = await response.json() as { error: string };
40
+
41
+ expect(response.status).toBe(400);
42
+ expect(body.error).toBe('Invalid input');
43
+ });
44
+
45
+ test('should handle UnauthorizedException', async () => {
46
+ const context = createContext();
47
+ const error = new UnauthorizedException('Not authenticated');
48
+
49
+ const response = await handleError(error, context);
50
+ const body = await response.json() as { error: string };
51
+
52
+ expect(response.status).toBe(401);
53
+ expect(body.error).toBe('Not authenticated');
54
+ });
55
+
56
+ test('should handle ForbiddenException', async () => {
57
+ const context = createContext();
58
+ const error = new ForbiddenException('Access denied');
59
+
60
+ const response = await handleError(error, context);
61
+ const body = await response.json() as { error: string };
62
+
63
+ expect(response.status).toBe(403);
64
+ expect(body.error).toBe('Access denied');
65
+ });
66
+
67
+ test('should handle NotFoundException', async () => {
68
+ const context = createContext();
69
+ const error = new NotFoundException('Resource not found');
70
+
71
+ const response = await handleError(error, context);
72
+ const body = await response.json() as { error: string };
73
+
74
+ expect(response.status).toBe(404);
75
+ expect(body.error).toBe('Resource not found');
76
+ });
77
+
78
+ test('should include error code in response when present', async () => {
79
+ const context = createContext();
80
+ // HttpException 构造函数: (status, message, details?, code?, messageParams?)
81
+ const error = new HttpException(400, 'Validation failed', undefined, 'E001');
82
+
83
+ const response = await handleError(error, context);
84
+ const body = await response.json() as { error: string; code: string };
85
+
86
+ expect(body.code).toBe('E001');
87
+ });
88
+
89
+ test('should include details in response when present', async () => {
90
+ const context = createContext();
91
+ const error = new HttpException(400, 'Validation failed');
92
+ (error as any).details = { field: 'email', issue: 'invalid format' };
93
+
94
+ const response = await handleError(error, context);
95
+ const body = await response.json() as { error: string; details: unknown };
96
+
97
+ expect(body.details).toEqual({ field: 'email', issue: 'invalid format' });
98
+ });
57
99
  });
58
100
 
59
- test('should fallback to 500 for unknown errors', async () => {
60
- const ctx = createContext();
61
- const response = await handleError(new Error('unknown'), ctx);
62
- expect(response.status).toBe(500);
63
- const data = await response.json();
64
- expect(data.error).toBe('Internal Server Error');
101
+ describe('ValidationError handling', () => {
102
+ test('should handle ValidationError', async () => {
103
+ const context = createContext();
104
+ const error = new ValidationError('Validation failed', [
105
+ { path: 'email', message: 'Invalid email format' },
106
+ ]);
107
+
108
+ const response = await handleError(error, context);
109
+ const body = await response.json() as { error: string; code: string; issues: unknown[] };
110
+
111
+ expect(response.status).toBe(400);
112
+ expect(body.error).toBe('Validation failed');
113
+ expect(body.code).toBe('VALIDATION_FAILED');
114
+ expect(body.issues).toHaveLength(1);
115
+ });
116
+
117
+ test('should include all validation issues', async () => {
118
+ const context = createContext();
119
+ const error = new ValidationError('Multiple errors', [
120
+ { path: 'email', message: 'Invalid email' },
121
+ { path: 'name', message: 'Name is required' },
122
+ { path: 'age', message: 'Age must be positive' },
123
+ ]);
124
+
125
+ const response = await handleError(error, context);
126
+ const body = await response.json() as { issues: unknown[] };
127
+
128
+ expect(body.issues).toHaveLength(3);
129
+ });
65
130
  });
66
- });
67
131
 
132
+ describe('Unknown error handling', () => {
133
+ test('should handle Error instance', async () => {
134
+ const context = createContext();
135
+ const error = new Error('Something went wrong');
136
+
137
+ const response = await handleError(error, context);
138
+ const body = await response.json() as { error: string; details?: string };
139
+
140
+ expect(response.status).toBe(500);
141
+ expect(body.error).toBe('Internal Server Error');
142
+ });
68
143
 
144
+ test('should handle string error', async () => {
145
+ const context = createContext();
146
+ const error = 'String error message';
147
+
148
+ const response = await handleError(error, context);
149
+ const body = await response.json() as { error: string };
150
+
151
+ expect(response.status).toBe(500);
152
+ expect(body.error).toBe('Internal Server Error');
153
+ });
154
+
155
+ test('should handle null error', async () => {
156
+ const context = createContext();
157
+ const error = null;
158
+
159
+ const response = await handleError(error, context);
160
+ const body = await response.json() as { error: string };
161
+
162
+ expect(response.status).toBe(500);
163
+ expect(body.error).toBe('Internal Server Error');
164
+ });
165
+
166
+ test('should handle undefined error', async () => {
167
+ const context = createContext();
168
+ const error = undefined;
169
+
170
+ const response = await handleError(error, context);
171
+ const body = await response.json() as { error: string };
172
+
173
+ expect(response.status).toBe(500);
174
+ expect(body.error).toBe('Internal Server Error');
175
+ });
176
+ });
177
+ });
@@ -0,0 +1,105 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { ErrorMessageI18n, type SupportedLanguage } from '../../src/error/i18n';
4
+ import { ErrorCode } from '../../src/error/error-codes';
5
+
6
+ describe('Error I18n', () => {
7
+ describe('ErrorMessageI18n.getMessage', () => {
8
+ test('should return English message by default', () => {
9
+ const message = ErrorMessageI18n.getMessage(ErrorCode.INTERNAL_ERROR);
10
+ expect(message.toLowerCase()).toContain('internal');
11
+ });
12
+
13
+ test('should return Chinese message when specified', () => {
14
+ const message = ErrorMessageI18n.getMessage(ErrorCode.INTERNAL_ERROR, 'zh-CN');
15
+ expect(message).toBe('服务器内部错误');
16
+ });
17
+
18
+ test('should return Japanese message when available', () => {
19
+ const message = ErrorMessageI18n.getMessage(ErrorCode.INTERNAL_ERROR, 'ja');
20
+ expect(message).toBe('サーバー内部エラー');
21
+ });
22
+
23
+ test('should return Korean message when available', () => {
24
+ const message = ErrorMessageI18n.getMessage(ErrorCode.INTERNAL_ERROR, 'ko');
25
+ expect(message).toBe('서버 내부 오류');
26
+ });
27
+
28
+ test('should fallback to English when translation not available', () => {
29
+ // Use an error code that might not have translations in all languages
30
+ const message = ErrorMessageI18n.getMessage(ErrorCode.DATABASE_POOL_EXHAUSTED, 'ja');
31
+ // Should fallback to English
32
+ expect(message).toBeDefined();
33
+ });
34
+
35
+ test('should return message for auth errors', () => {
36
+ const messages = {
37
+ en: ErrorMessageI18n.getMessage(ErrorCode.AUTH_REQUIRED, 'en'),
38
+ zhCN: ErrorMessageI18n.getMessage(ErrorCode.AUTH_REQUIRED, 'zh-CN'),
39
+ };
40
+
41
+ expect(messages.en.toLowerCase()).toContain('authentication');
42
+ expect(messages.zhCN).toBe('需要认证');
43
+ });
44
+
45
+ test('should return message for validation errors', () => {
46
+ const message = ErrorMessageI18n.getMessage(ErrorCode.VALIDATION_FAILED, 'zh-CN');
47
+ expect(message).toBe('验证失败');
48
+ });
49
+
50
+ test('should support message parameters', () => {
51
+ // getMessage supports params as third argument
52
+ const message = ErrorMessageI18n.getMessage(ErrorCode.INTERNAL_ERROR, 'en', { detail: 'test' });
53
+ expect(message).toBeDefined();
54
+ });
55
+ });
56
+
57
+ describe('default language', () => {
58
+ test('should support multiple languages', () => {
59
+ // Test that different languages return different messages
60
+ const enMessage = ErrorMessageI18n.getMessage(ErrorCode.INTERNAL_ERROR, 'en');
61
+ const zhMessage = ErrorMessageI18n.getMessage(ErrorCode.INTERNAL_ERROR, 'zh-CN');
62
+
63
+ // English and Chinese messages should be different
64
+ expect(enMessage).not.toBe(zhMessage);
65
+ expect(zhMessage).toBe('服务器内部错误');
66
+ });
67
+ });
68
+
69
+ describe('ErrorMessageI18n.parseLanguageFromHeader', () => {
70
+ test('should parse zh-CN from header', () => {
71
+ const lang = ErrorMessageI18n.parseLanguageFromHeader('zh-CN,zh;q=0.9,en;q=0.8');
72
+ expect(lang).toBe('zh-CN');
73
+ });
74
+
75
+ test('should parse Japanese', () => {
76
+ const lang = ErrorMessageI18n.parseLanguageFromHeader('ja,en;q=0.8');
77
+ expect(lang).toBe('ja');
78
+ });
79
+
80
+ test('should parse Korean', () => {
81
+ const lang = ErrorMessageI18n.parseLanguageFromHeader('ko-KR,ko;q=0.9');
82
+ expect(lang).toBe('ko');
83
+ });
84
+
85
+ test('should return English by default', () => {
86
+ const lang = ErrorMessageI18n.parseLanguageFromHeader('fr,de;q=0.8');
87
+ expect(lang).toBe('en');
88
+ });
89
+
90
+ test('should return English when header is null', () => {
91
+ const lang = ErrorMessageI18n.parseLanguageFromHeader(null);
92
+ expect(lang).toBe('en');
93
+ });
94
+
95
+ test('should return English when header is undefined', () => {
96
+ const lang = ErrorMessageI18n.parseLanguageFromHeader(undefined);
97
+ expect(lang).toBe('en');
98
+ });
99
+
100
+ test('should handle zh without region code', () => {
101
+ const lang = ErrorMessageI18n.parseLanguageFromHeader('zh');
102
+ expect(lang).toBe('zh-CN');
103
+ });
104
+ });
105
+ });