@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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/tests/auth/auth-decorators.test.ts +241 -0
  3. package/tests/auth/oauth2-service.test.ts +318 -0
  4. package/tests/cache/cache-decorators-extended.test.ts +272 -0
  5. package/tests/cache/cache-interceptors.test.ts +534 -0
  6. package/tests/cache/cache-service-proxy.test.ts +246 -0
  7. package/tests/cache/memory-cache-store.test.ts +155 -0
  8. package/tests/cache/redis-cache-store.test.ts +199 -0
  9. package/tests/config/config-center-integration.test.ts +334 -0
  10. package/tests/config/config-module-extended.test.ts +165 -0
  11. package/tests/controller/param-binder.test.ts +333 -0
  12. package/tests/error/error-handler.test.ts +166 -57
  13. package/tests/error/i18n-extended.test.ts +105 -0
  14. package/tests/events/event-listener-scanner.test.ts +114 -0
  15. package/tests/events/event-module.test.ts +133 -302
  16. package/tests/extensions/logger-module.test.ts +158 -0
  17. package/tests/files/file-storage.test.ts +136 -0
  18. package/tests/interceptor/base-interceptor.test.ts +605 -0
  19. package/tests/interceptor/builtin/cache-interceptor.test.ts +233 -86
  20. package/tests/interceptor/builtin/log-interceptor.test.ts +469 -0
  21. package/tests/interceptor/builtin/permission-interceptor.test.ts +219 -120
  22. package/tests/interceptor/interceptor-chain.test.ts +241 -189
  23. package/tests/interceptor/interceptor-metadata.test.ts +221 -0
  24. package/tests/microservice/circuit-breaker.test.ts +221 -0
  25. package/tests/microservice/service-client-decorators.test.ts +86 -0
  26. package/tests/microservice/service-client-interceptors.test.ts +274 -0
  27. package/tests/microservice/service-registry-decorators.test.ts +147 -0
  28. package/tests/microservice/tracer.test.ts +213 -0
  29. package/tests/microservice/tracing-collectors.test.ts +168 -0
  30. package/tests/middleware/builtin/middleware-builtin-extended.test.ts +237 -0
  31. package/tests/middleware/builtin/rate-limit.test.ts +257 -0
  32. package/tests/middleware/middleware-decorators.test.ts +222 -0
  33. package/tests/middleware/middleware-pipeline.test.ts +160 -0
  34. package/tests/queue/queue-decorators.test.ts +139 -0
  35. package/tests/queue/queue-service.test.ts +191 -0
  36. package/tests/request/body-parser-extended.test.ts +291 -0
  37. package/tests/request/request-wrapper.test.ts +319 -0
  38. package/tests/router/router-decorators.test.ts +260 -0
  39. package/tests/router/router-extended.test.ts +298 -0
  40. package/tests/security/guards/reflector.test.ts +188 -0
  41. package/tests/security/security-filter.test.ts +182 -0
  42. package/tests/security/security-module-extended.test.ts +133 -0
  43. package/tests/session/memory-session-store.test.ts +172 -0
  44. package/tests/session/session-decorators.test.ts +163 -0
  45. package/tests/swagger/ui.test.ts +212 -0
@@ -0,0 +1,469 @@
1
+ import { describe, expect, test, beforeEach, mock } from 'bun:test';
2
+ import 'reflect-metadata';
3
+
4
+ import { Container } from '../../../src/di/container';
5
+ import { Context } from '../../../src/core/context';
6
+ import {
7
+ LogInterceptor,
8
+ Log,
9
+ getLogMetadata,
10
+ LOG_METADATA_KEY,
11
+ type LogOptions,
12
+ } from '../../../src/interceptor/builtin/log-interceptor';
13
+ import { LOGGER_TOKEN, LogLevel, type LogEntry } from '../../../src/extensions';
14
+ import { LoggerExtension } from '../../../src/extensions/logger-extension';
15
+
16
+ describe('Log Decorator', () => {
17
+ test('should set log metadata with default options', () => {
18
+ class TestService {
19
+ @Log()
20
+ public testMethod(): string {
21
+ return 'test';
22
+ }
23
+ }
24
+
25
+ const metadata = getLogMetadata(TestService.prototype, 'testMethod');
26
+ expect(metadata).toBeDefined();
27
+ expect(metadata?.level).toBe('info');
28
+ expect(metadata?.logArgs).toBe(false);
29
+ expect(metadata?.logResult).toBe(false);
30
+ expect(metadata?.logDuration).toBe(true);
31
+ });
32
+
33
+ test('should set log metadata with custom options', () => {
34
+ class TestService {
35
+ @Log({
36
+ level: 'debug',
37
+ message: 'Custom log message',
38
+ logArgs: true,
39
+ logResult: true,
40
+ logDuration: false,
41
+ })
42
+ public testMethod(): string {
43
+ return 'test';
44
+ }
45
+ }
46
+
47
+ const metadata = getLogMetadata(TestService.prototype, 'testMethod');
48
+ expect(metadata).toBeDefined();
49
+ expect(metadata?.level).toBe('debug');
50
+ expect(metadata?.message).toBe('Custom log message');
51
+ expect(metadata?.logArgs).toBe(true);
52
+ expect(metadata?.logResult).toBe(true);
53
+ expect(metadata?.logDuration).toBe(false);
54
+ });
55
+
56
+ test('should set log metadata with warn level', () => {
57
+ class TestService {
58
+ @Log({ level: 'warn' })
59
+ public warnMethod(): void {}
60
+ }
61
+
62
+ const metadata = getLogMetadata(TestService.prototype, 'warnMethod');
63
+ expect(metadata?.level).toBe('warn');
64
+ });
65
+
66
+ test('should set log metadata with error level', () => {
67
+ class TestService {
68
+ @Log({ level: 'error' })
69
+ public errorMethod(): void {}
70
+ }
71
+
72
+ const metadata = getLogMetadata(TestService.prototype, 'errorMethod');
73
+ expect(metadata?.level).toBe('error');
74
+ });
75
+
76
+ test('should return undefined for non-decorated method', () => {
77
+ class TestService {
78
+ public normalMethod(): void {}
79
+ }
80
+
81
+ const metadata = getLogMetadata(TestService.prototype, 'normalMethod');
82
+ expect(metadata).toBeUndefined();
83
+ });
84
+
85
+ test('should return undefined for invalid target', () => {
86
+ const metadata = getLogMetadata(null, 'method');
87
+ expect(metadata).toBeUndefined();
88
+
89
+ const metadata2 = getLogMetadata(undefined, 'method');
90
+ expect(metadata2).toBeUndefined();
91
+
92
+ const metadata3 = getLogMetadata('string', 'method');
93
+ expect(metadata3).toBeUndefined();
94
+ });
95
+ });
96
+
97
+ describe('LogInterceptor', () => {
98
+ let container: Container;
99
+ let interceptor: LogInterceptor;
100
+ let logEntries: LogEntry[];
101
+
102
+ beforeEach(() => {
103
+ container = new Container();
104
+ interceptor = new LogInterceptor();
105
+ logEntries = [];
106
+
107
+ // 注册 Logger
108
+ const loggerExtension = new LoggerExtension({
109
+ prefix: 'Test',
110
+ level: LogLevel.DEBUG,
111
+ sink(entry) {
112
+ logEntries.push(entry);
113
+ },
114
+ });
115
+ loggerExtension.register(container);
116
+ });
117
+
118
+ test('should log method execution with default options', async () => {
119
+ class TestService {
120
+ @Log()
121
+ public testMethod(): string {
122
+ return 'result';
123
+ }
124
+ }
125
+
126
+ const service = new TestService();
127
+ const result = await interceptor.execute(
128
+ service,
129
+ 'testMethod',
130
+ service.testMethod.bind(service),
131
+ [],
132
+ container,
133
+ undefined,
134
+ );
135
+
136
+ expect(result).toBe('result');
137
+ expect(logEntries.length).toBe(2); // Start + Completed
138
+ expect(logEntries[0].message).toContain('Executing testMethod - Start');
139
+ expect(logEntries[1].message).toContain('Executing testMethod - Completed');
140
+ });
141
+
142
+ test('should log with custom message', async () => {
143
+ class TestService {
144
+ @Log({ message: 'Processing user request' })
145
+ public processUser(): void {}
146
+ }
147
+
148
+ const service = new TestService();
149
+ await interceptor.execute(
150
+ service,
151
+ 'processUser',
152
+ service.processUser.bind(service),
153
+ [],
154
+ container,
155
+ undefined,
156
+ );
157
+
158
+ expect(logEntries[0].message).toContain('Processing user request - Start');
159
+ expect(logEntries[1].message).toContain('Processing user request - Completed');
160
+ });
161
+
162
+ test('should log method arguments when logArgs is true', async () => {
163
+ class TestService {
164
+ @Log({ logArgs: true })
165
+ public greet(name: string, age: number): string {
166
+ return `Hello ${name}, you are ${age}`;
167
+ }
168
+ }
169
+
170
+ const service = new TestService();
171
+ await interceptor.execute(
172
+ service,
173
+ 'greet',
174
+ service.greet.bind(service),
175
+ ['Alice', 30],
176
+ container,
177
+ undefined,
178
+ );
179
+
180
+ // 检查 Start 日志包含参数信息
181
+ const startLog = logEntries[0];
182
+ expect(startLog.message).toContain('Start');
183
+ });
184
+
185
+ test('should log result when logResult is true', async () => {
186
+ class TestService {
187
+ @Log({ logResult: true })
188
+ public calculate(): number {
189
+ return 42;
190
+ }
191
+ }
192
+
193
+ const service = new TestService();
194
+ const result = await interceptor.execute(
195
+ service,
196
+ 'calculate',
197
+ service.calculate.bind(service),
198
+ [],
199
+ container,
200
+ undefined,
201
+ );
202
+
203
+ expect(result).toBe(42);
204
+ // 检查 Completed 日志存在
205
+ const completedLog = logEntries[1];
206
+ expect(completedLog.message).toContain('Completed');
207
+ });
208
+
209
+ test('should log duration by default', async () => {
210
+ class TestService {
211
+ @Log()
212
+ public slowMethod(): string {
213
+ return 'done';
214
+ }
215
+ }
216
+
217
+ const service = new TestService();
218
+ const result = await interceptor.execute(
219
+ service,
220
+ 'slowMethod',
221
+ service.slowMethod.bind(service),
222
+ [],
223
+ container,
224
+ undefined,
225
+ );
226
+
227
+ expect(result).toBe('done');
228
+ // 检查日志存在
229
+ expect(logEntries.length).toBe(2);
230
+ expect(logEntries[1].message).toContain('Completed');
231
+ });
232
+
233
+ test('should not log duration when logDuration is false', async () => {
234
+ class TestService {
235
+ @Log({ logDuration: false })
236
+ public quickMethod(): string {
237
+ return 'done';
238
+ }
239
+ }
240
+
241
+ const service = new TestService();
242
+ await interceptor.execute(
243
+ service,
244
+ 'quickMethod',
245
+ service.quickMethod.bind(service),
246
+ [],
247
+ container,
248
+ undefined,
249
+ );
250
+
251
+ // Completed 日志不应包含 duration
252
+ const completedLog = logEntries[1];
253
+ // 当没有额外数据时,data 可能为空或未定义
254
+ if (completedLog.data) {
255
+ expect((completedLog.data as any).duration).toBeUndefined();
256
+ }
257
+ });
258
+
259
+ test('should log at debug level', async () => {
260
+ class TestService {
261
+ @Log({ level: 'debug' })
262
+ public debugMethod(): void {}
263
+ }
264
+
265
+ const service = new TestService();
266
+ await interceptor.execute(
267
+ service,
268
+ 'debugMethod',
269
+ service.debugMethod.bind(service),
270
+ [],
271
+ container,
272
+ undefined,
273
+ );
274
+
275
+ expect(logEntries[0].level).toBe(LogLevel.DEBUG);
276
+ });
277
+
278
+ test('should log at warn level', async () => {
279
+ class TestService {
280
+ @Log({ level: 'warn' })
281
+ public warnMethod(): void {}
282
+ }
283
+
284
+ const service = new TestService();
285
+ await interceptor.execute(
286
+ service,
287
+ 'warnMethod',
288
+ service.warnMethod.bind(service),
289
+ [],
290
+ container,
291
+ undefined,
292
+ );
293
+
294
+ expect(logEntries[0].level).toBe(LogLevel.WARN);
295
+ });
296
+
297
+ test('should log at error level', async () => {
298
+ class TestService {
299
+ @Log({ level: 'error' })
300
+ public errorMethod(): void {}
301
+ }
302
+
303
+ const service = new TestService();
304
+ await interceptor.execute(
305
+ service,
306
+ 'errorMethod',
307
+ service.errorMethod.bind(service),
308
+ [],
309
+ container,
310
+ undefined,
311
+ );
312
+
313
+ expect(logEntries[0].level).toBe(LogLevel.ERROR);
314
+ });
315
+
316
+ test('should log error when method throws', async () => {
317
+ class TestService {
318
+ @Log()
319
+ public failingMethod(): void {
320
+ throw new Error('Test error');
321
+ }
322
+ }
323
+
324
+ const service = new TestService();
325
+
326
+ await expect(
327
+ interceptor.execute(
328
+ service,
329
+ 'failingMethod',
330
+ service.failingMethod.bind(service),
331
+ [],
332
+ container,
333
+ undefined,
334
+ ),
335
+ ).rejects.toThrow('Test error');
336
+
337
+ // 应该有 Start 和 Failed 日志
338
+ expect(logEntries.length).toBe(2);
339
+ expect(logEntries[1].message).toContain('Failed');
340
+ });
341
+
342
+ test('should log duration on error', async () => {
343
+ class TestService {
344
+ @Log()
345
+ public failingMethod(): void {
346
+ throw new Error('Error');
347
+ }
348
+ }
349
+
350
+ const service = new TestService();
351
+
352
+ await expect(
353
+ interceptor.execute(
354
+ service,
355
+ 'failingMethod',
356
+ service.failingMethod.bind(service),
357
+ [],
358
+ container,
359
+ undefined,
360
+ ),
361
+ ).rejects.toThrow();
362
+
363
+ const failedLog = logEntries[1];
364
+ expect(failedLog.message).toContain('Failed');
365
+ });
366
+
367
+ test('should handle async methods', async () => {
368
+ class TestService {
369
+ @Log()
370
+ public async asyncMethod(): Promise<string> {
371
+ await new Promise((resolve) => setTimeout(resolve, 10));
372
+ return 'async result';
373
+ }
374
+ }
375
+
376
+ const service = new TestService();
377
+ const result = await interceptor.execute(
378
+ service,
379
+ 'asyncMethod',
380
+ service.asyncMethod.bind(service),
381
+ [],
382
+ container,
383
+ undefined,
384
+ );
385
+
386
+ expect(result).toBe('async result');
387
+ expect(logEntries.length).toBe(2);
388
+ });
389
+
390
+ test('should use console when logger is not available', async () => {
391
+ const emptyContainer = new Container();
392
+ const consoleLogs: string[] = [];
393
+ const originalLog = console.log;
394
+ console.log = (...args: any[]) => {
395
+ consoleLogs.push(args.join(' '));
396
+ };
397
+
398
+ try {
399
+ class TestService {
400
+ @Log()
401
+ public testMethod(): string {
402
+ return 'test';
403
+ }
404
+ }
405
+
406
+ const service = new TestService();
407
+ await interceptor.execute(
408
+ service,
409
+ 'testMethod',
410
+ service.testMethod.bind(service),
411
+ [],
412
+ emptyContainer,
413
+ undefined,
414
+ );
415
+
416
+ expect(consoleLogs.length).toBeGreaterThan(0);
417
+ expect(consoleLogs.some((log) => log.includes('[INFO]'))).toBe(true);
418
+ } finally {
419
+ console.log = originalLog;
420
+ }
421
+ });
422
+
423
+ test('should handle non-Error thrown values', async () => {
424
+ class TestService {
425
+ @Log()
426
+ public throwString(): void {
427
+ throw 'string error';
428
+ }
429
+ }
430
+
431
+ const service = new TestService();
432
+
433
+ await expect(
434
+ interceptor.execute(
435
+ service,
436
+ 'throwString',
437
+ service.throwString.bind(service),
438
+ [],
439
+ container,
440
+ undefined,
441
+ ),
442
+ ).rejects.toBe('string error');
443
+
444
+ const failedLog = logEntries[1];
445
+ expect(failedLog.message).toContain('Failed');
446
+ });
447
+
448
+ test('should work without metadata (using defaults)', async () => {
449
+ class TestService {
450
+ public noDecoratorMethod(): string {
451
+ return 'no decorator';
452
+ }
453
+ }
454
+
455
+ const service = new TestService();
456
+ const result = await interceptor.execute(
457
+ service,
458
+ 'noDecoratorMethod',
459
+ service.noDecoratorMethod.bind(service),
460
+ [],
461
+ container,
462
+ undefined,
463
+ );
464
+
465
+ expect(result).toBe('no decorator');
466
+ // 即使没有装饰器,拦截器也应该记录日志(使用默认选项)
467
+ expect(logEntries.length).toBe(2);
468
+ });
469
+ });