@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,158 @@
1
+ import { describe, expect, test, beforeEach } from 'bun:test';
2
+ import 'reflect-metadata';
3
+
4
+ import { Container } from '../../src/di/container';
5
+ import { MODULE_METADATA_KEY } from '../../src/di/module';
6
+ import { LoggerModule, type LoggerModuleOptions } from '../../src/extensions/logger-module';
7
+ import { LOGGER_TOKEN, LogLevel, type LogEntry } from '../../src/extensions';
8
+ import type { Logger } from '@dangao/logsmith';
9
+
10
+ describe('LoggerModule', () => {
11
+ beforeEach(() => {
12
+ // 清除模块元数据
13
+ Reflect.deleteMetadata(MODULE_METADATA_KEY, LoggerModule);
14
+ });
15
+
16
+ test('should create logger module with default options', () => {
17
+ LoggerModule.forRoot();
18
+
19
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, LoggerModule);
20
+ expect(metadata).toBeDefined();
21
+ expect(metadata.extensions).toBeDefined();
22
+ expect(metadata.extensions.length).toBe(1);
23
+ // 默认启用请求日志
24
+ expect(metadata.middlewares).toBeDefined();
25
+ expect(metadata.middlewares.length).toBe(1);
26
+ });
27
+
28
+ test('should create logger module with custom logger options', () => {
29
+ const entries: LogEntry[] = [];
30
+ const options: LoggerModuleOptions = {
31
+ logger: {
32
+ prefix: 'TestApp',
33
+ level: LogLevel.DEBUG,
34
+ sink(entry) {
35
+ entries.push(entry);
36
+ },
37
+ },
38
+ };
39
+
40
+ LoggerModule.forRoot(options);
41
+
42
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, LoggerModule);
43
+ expect(metadata).toBeDefined();
44
+ expect(metadata.extensions).toBeDefined();
45
+ expect(metadata.extensions.length).toBe(1);
46
+ });
47
+
48
+ test('should disable request logging when enableRequestLogging is false', () => {
49
+ LoggerModule.forRoot({
50
+ enableRequestLogging: false,
51
+ });
52
+
53
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, LoggerModule);
54
+ expect(metadata).toBeDefined();
55
+ expect(metadata.extensions.length).toBe(1);
56
+ // 禁用请求日志时,middlewares 应该为空
57
+ expect(metadata.middlewares.length).toBe(0);
58
+ });
59
+
60
+ test('should enable request logging with custom prefix', () => {
61
+ LoggerModule.forRoot({
62
+ enableRequestLogging: true,
63
+ requestLoggingPrefix: '[HTTP]',
64
+ });
65
+
66
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, LoggerModule);
67
+ expect(metadata).toBeDefined();
68
+ expect(metadata.middlewares.length).toBe(1);
69
+ });
70
+
71
+ test('should register logger into container via extension', () => {
72
+ const entries: LogEntry[] = [];
73
+ LoggerModule.forRoot({
74
+ logger: {
75
+ prefix: 'Test',
76
+ level: LogLevel.DEBUG,
77
+ sink(entry) {
78
+ entries.push(entry);
79
+ },
80
+ },
81
+ });
82
+
83
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, LoggerModule);
84
+ const extension = metadata.extensions[0];
85
+
86
+ // 模拟扩展注册到容器
87
+ const container = new Container();
88
+ extension.register(container);
89
+
90
+ const logger = container.resolve<Logger>(LOGGER_TOKEN);
91
+ expect(logger).toBeDefined();
92
+
93
+ logger.info('test message');
94
+ expect(entries.length).toBe(1);
95
+ expect(entries[0].message).toBe('test message');
96
+ });
97
+
98
+ test('should work with container via extension registration', async () => {
99
+ const entries: LogEntry[] = [];
100
+ LoggerModule.forRoot({
101
+ logger: {
102
+ sink(entry) {
103
+ entries.push(entry);
104
+ },
105
+ },
106
+ enableRequestLogging: false,
107
+ });
108
+
109
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, LoggerModule);
110
+ const container = new Container();
111
+
112
+ // 模拟模块注册过程:手动注册扩展
113
+ for (const extension of metadata.extensions) {
114
+ if (extension && typeof extension.register === 'function') {
115
+ extension.register(container);
116
+ }
117
+ }
118
+
119
+ const logger = container.resolve<Logger>(LOGGER_TOKEN);
120
+ expect(logger).toBeDefined();
121
+
122
+ logger.warn('warning message');
123
+ expect(entries.length).toBe(1);
124
+ expect(entries[0].level).toBe(LogLevel.WARN);
125
+ });
126
+
127
+ test('should preserve existing metadata when calling forRoot multiple times', () => {
128
+ // 第一次调用
129
+ LoggerModule.forRoot({
130
+ enableRequestLogging: false,
131
+ });
132
+
133
+ const metadata1 = Reflect.getMetadata(MODULE_METADATA_KEY, LoggerModule);
134
+ expect(metadata1.extensions.length).toBe(1);
135
+ expect(metadata1.middlewares.length).toBe(0);
136
+
137
+ // 清除元数据模拟重新配置
138
+ Reflect.deleteMetadata(MODULE_METADATA_KEY, LoggerModule);
139
+
140
+ // 第二次调用使用不同选项
141
+ LoggerModule.forRoot({
142
+ enableRequestLogging: true,
143
+ requestLoggingPrefix: '[API]',
144
+ });
145
+
146
+ const metadata2 = Reflect.getMetadata(MODULE_METADATA_KEY, LoggerModule);
147
+ expect(metadata2.extensions.length).toBe(1);
148
+ expect(metadata2.middlewares.length).toBe(1);
149
+ });
150
+
151
+ test('should use default values when options are not provided', () => {
152
+ LoggerModule.forRoot({});
153
+
154
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, LoggerModule);
155
+ // enableRequestLogging 默认不是 false,所以应该启用
156
+ expect(metadata.middlewares.length).toBe(1);
157
+ });
158
+ });
@@ -0,0 +1,136 @@
1
+ import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
2
+ import { rmdir, unlink, readdir, mkdir } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+
5
+ import { FileStorage } from '../../src/files/storage';
6
+
7
+ describe('FileStorage', () => {
8
+ const testDir = '/tmp/bun-server-test-uploads';
9
+
10
+ beforeEach(async () => {
11
+ // Clean up before each test to ensure isolation
12
+ try {
13
+ const files = await readdir(testDir);
14
+ for (const file of files) {
15
+ await unlink(join(testDir, file));
16
+ }
17
+ await rmdir(testDir);
18
+ } catch {
19
+ // Directory may not exist yet
20
+ }
21
+ await mkdir(testDir, { recursive: true });
22
+ });
23
+
24
+ afterEach(async () => {
25
+ try {
26
+ // Clean up test directory
27
+ const files = await readdir(testDir);
28
+ for (const file of files) {
29
+ await unlink(join(testDir, file));
30
+ }
31
+ await rmdir(testDir);
32
+ } catch {
33
+ // Ignore cleanup errors
34
+ }
35
+ });
36
+
37
+ describe('saveFile', () => {
38
+ test('should save file to destination', async () => {
39
+ const uniqueName = `test-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`;
40
+ const file = new File(['test content'], uniqueName, { type: 'text/plain' });
41
+
42
+ const result = await FileStorage.saveFile(
43
+ file,
44
+ { dest: testDir },
45
+ 'testField',
46
+ );
47
+
48
+ expect(result.fieldName).toBe('testField');
49
+ expect(result.filename).toBe(uniqueName);
50
+ expect(result.originalName).toBe(uniqueName);
51
+ expect(result.mimeType).toContain('text/plain');
52
+ expect(result.size).toBe(12);
53
+ expect(result.path).toContain(testDir);
54
+ });
55
+
56
+ test('should use custom filename', async () => {
57
+ const file = new File(['content'], 'original.txt');
58
+
59
+ const result = await FileStorage.saveFile(
60
+ file,
61
+ { dest: testDir, filename: 'custom.txt' },
62
+ 'myField',
63
+ );
64
+
65
+ expect(result.filename).toBe('custom.txt');
66
+ });
67
+
68
+ test('should sanitize filename', async () => {
69
+ const file = new File(['content'], 'file with spaces & chars!.txt');
70
+
71
+ const result = await FileStorage.saveFile(
72
+ file,
73
+ { dest: testDir },
74
+ 'field',
75
+ );
76
+
77
+ // Should not contain spaces or special chars
78
+ expect(result.filename).not.toContain(' ');
79
+ expect(result.filename).not.toContain('&');
80
+ expect(result.filename).not.toContain('!');
81
+ });
82
+
83
+ test('should handle file without extension', async () => {
84
+ // File without extension still gets default mime type from Bun
85
+ const file = new File(['data'], 'noext');
86
+
87
+ const result = await FileStorage.saveFile(
88
+ file,
89
+ { dest: testDir },
90
+ 'field',
91
+ );
92
+
93
+ // mimeType should be defined
94
+ expect(result.mimeType).toBeDefined();
95
+ });
96
+
97
+ test('should prevent overwrite by default', async () => {
98
+ const file1 = new File(['content1'], 'same.txt');
99
+ const file2 = new File(['content2'], 'same.txt');
100
+
101
+ await FileStorage.saveFile(file1, { dest: testDir }, 'field1');
102
+ const result2 = await FileStorage.saveFile(file2, { dest: testDir }, 'field2');
103
+
104
+ // Second file should have a different name
105
+ expect(result2.filename).not.toBe('same.txt');
106
+ expect(result2.filename).toContain('same');
107
+ });
108
+
109
+ test('should allow overwrite when specified', async () => {
110
+ const file1 = new File(['content1'], 'overwrite.txt');
111
+ const file2 = new File(['content2'], 'overwrite.txt');
112
+
113
+ await FileStorage.saveFile(file1, { dest: testDir }, 'field1');
114
+ const result2 = await FileStorage.saveFile(
115
+ file2,
116
+ { dest: testDir, overwrite: true },
117
+ 'field2',
118
+ );
119
+
120
+ expect(result2.filename).toBe('overwrite.txt');
121
+ });
122
+
123
+ test('should create destination directory if not exists', async () => {
124
+ const newDir = join(testDir, 'subdir', 'nested');
125
+ const file = new File(['content'], 'test.txt');
126
+
127
+ const result = await FileStorage.saveFile(
128
+ file,
129
+ { dest: newDir },
130
+ 'field',
131
+ );
132
+
133
+ expect(result.path).toContain(newDir);
134
+ });
135
+ });
136
+ });