@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
@@ -1,182 +1,281 @@
1
+ import { describe, expect, test, beforeEach } from 'bun:test';
1
2
  import 'reflect-metadata';
2
- import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
3
- import { Application } from '../../../src/core/application';
4
- import { Controller, ControllerRegistry } from '../../../src/controller/controller';
5
- import { GET } from '../../../src/router/decorators';
6
- import { RouteRegistry } from '../../../src/router/registry';
3
+
7
4
  import {
8
5
  Permission,
6
+ getPermissionMetadata,
9
7
  PermissionInterceptor,
10
8
  PERMISSION_METADATA_KEY,
11
- InterceptorRegistry,
12
- INTERCEPTOR_REGISTRY_TOKEN,
9
+ type PermissionOptions,
13
10
  type PermissionService,
14
- } from '../../../src/interceptor';
11
+ } from '../../../src/interceptor/builtin/permission-interceptor';
12
+ import { Container } from '../../../src/di/container';
13
+ import { Context } from '../../../src/core/context';
15
14
  import { ForbiddenException } from '../../../src/error';
16
- import { getTestPort } from '../../utils/test-port';
17
15
 
18
- describe('PermissionInterceptor', () => {
19
- let app: Application;
20
- let port: number;
21
- let interceptorRegistry: InterceptorRegistry;
16
+ describe('Permission Decorator', () => {
17
+ test('should set permission metadata with required options', () => {
18
+ class TestController {
19
+ @Permission({ resource: 'users', action: 'read' })
20
+ public getUsers(): void {}
21
+ }
22
22
 
23
- beforeEach(() => {
24
- port = getTestPort();
25
- app = new Application({ port });
26
- interceptorRegistry = app.getContainer().resolve<InterceptorRegistry>(
27
- INTERCEPTOR_REGISTRY_TOKEN,
28
- );
29
- interceptorRegistry.register(PERMISSION_METADATA_KEY, new PermissionInterceptor());
23
+ const metadata = getPermissionMetadata(TestController.prototype, 'getUsers');
24
+ expect(metadata).toBeDefined();
25
+ expect(metadata?.resource).toBe('users');
26
+ expect(metadata?.action).toBe('read');
27
+ expect(metadata?.allowAnonymous).toBe(false);
30
28
  });
31
29
 
32
- afterEach(async () => {
33
- if (app) {
34
- await app.stop();
30
+ test('should set permission metadata with allowAnonymous', () => {
31
+ class TestController {
32
+ @Permission({ resource: 'posts', action: 'read', allowAnonymous: true })
33
+ public getPosts(): void {}
35
34
  }
36
- RouteRegistry.getInstance().clear();
37
- ControllerRegistry.getInstance().clear();
38
- interceptorRegistry.clear();
35
+
36
+ const metadata = getPermissionMetadata(TestController.prototype, 'getPosts');
37
+ expect(metadata?.allowAnonymous).toBe(true);
39
38
  });
40
39
 
41
- test('should allow access when permission check passes', async () => {
42
- // 创建权限服务实现
43
- class MockPermissionService implements PermissionService {
44
- public async check(
45
- userId: string | null,
46
- resource: string,
47
- action: string,
48
- ): Promise<boolean> {
49
- return userId === 'user1' && resource === 'user' && action === 'read';
50
- }
40
+ test('should set all options together', () => {
41
+ const options: PermissionOptions = {
42
+ resource: 'orders',
43
+ action: 'delete',
44
+ allowAnonymous: false,
45
+ };
46
+
47
+ class TestController {
48
+ @Permission(options)
49
+ public deleteOrder(): void {}
50
+ }
51
+
52
+ const metadata = getPermissionMetadata(TestController.prototype, 'deleteOrder');
53
+ expect(metadata?.resource).toBe('orders');
54
+ expect(metadata?.action).toBe('delete');
55
+ expect(metadata?.allowAnonymous).toBe(false);
56
+ });
57
+ });
58
+
59
+ describe('getPermissionMetadata', () => {
60
+ test('should return undefined for non-decorated method', () => {
61
+ class TestController {
62
+ public normalMethod(): void {}
51
63
  }
52
64
 
53
- // 注册权限服务
54
- app.getContainer().registerInstance(
65
+ const metadata = getPermissionMetadata(TestController.prototype, 'normalMethod');
66
+ expect(metadata).toBeUndefined();
67
+ });
68
+
69
+ test('should return undefined for null target', () => {
70
+ const metadata = getPermissionMetadata(null, 'method');
71
+ expect(metadata).toBeUndefined();
72
+ });
73
+
74
+ test('should return undefined for non-object target', () => {
75
+ const metadata = getPermissionMetadata('string', 'method');
76
+ expect(metadata).toBeUndefined();
77
+ });
78
+ });
79
+
80
+ describe('PermissionInterceptor', () => {
81
+ let container: Container;
82
+ let interceptor: PermissionInterceptor;
83
+ let mockPermissionService: PermissionService;
84
+
85
+ beforeEach(() => {
86
+ container = new Container();
87
+ interceptor = new PermissionInterceptor();
88
+ mockPermissionService = {
89
+ check: async (userId: string | null, resource: string, action: string) => {
90
+ // 模拟权限检查:用户 'admin' 有所有权限
91
+ if (userId === 'admin') return true;
92
+ // 用户 'user1' 只有读权限
93
+ if (userId === 'user1' && action === 'read') return true;
94
+ return false;
95
+ },
96
+ };
97
+ container.registerInstance(
55
98
  PermissionInterceptor.PERMISSION_SERVICE_TOKEN,
56
- new MockPermissionService(),
99
+ mockPermissionService,
57
100
  );
101
+ });
58
102
 
59
- @Controller('/api/users')
60
- class UserController {
61
- @GET('/:id')
62
- @Permission({ resource: 'user', action: 'read' })
63
- public getUser() {
64
- return { id: '123', name: 'Test User' };
103
+ test('should execute method without permission check when no metadata', async () => {
104
+ class TestController {
105
+ public normalMethod(): string {
106
+ return 'executed';
65
107
  }
66
108
  }
67
109
 
68
- app.registerController(UserController);
69
- await app.listen();
110
+ const controller = new TestController();
70
111
 
71
- const response = await fetch(`http://localhost:${port}/api/users/123`, {
72
- headers: { 'X-User-Id': 'user1' },
73
- });
112
+ const result = await interceptor.execute(
113
+ controller,
114
+ 'normalMethod',
115
+ controller.normalMethod.bind(controller),
116
+ [],
117
+ container,
118
+ );
74
119
 
75
- expect(response.status).toBe(200);
76
- const data = await response.json();
77
- expect(data.id).toBe('123');
120
+ expect(result).toBe('executed');
78
121
  });
79
122
 
80
- test('should deny access when permission check fails', async () => {
81
- // 创建权限服务实现
82
- class MockPermissionService implements PermissionService {
83
- public async check(
84
- userId: string | null,
85
- resource: string,
86
- action: string,
87
- ): Promise<boolean> {
88
- return false; // 总是拒绝
123
+ test('should allow anonymous access when allowAnonymous is true', async () => {
124
+ class TestController {
125
+ @Permission({ resource: 'public', action: 'read', allowAnonymous: true })
126
+ public getPublic(): string {
127
+ return 'public data';
89
128
  }
90
129
  }
91
130
 
92
- // 注册权限服务
93
- app.getContainer().registerInstance(
94
- PermissionInterceptor.PERMISSION_SERVICE_TOKEN,
95
- new MockPermissionService(),
131
+ const controller = new TestController();
132
+
133
+ const result = await interceptor.execute(
134
+ controller,
135
+ 'getPublic',
136
+ controller.getPublic.bind(controller),
137
+ [],
138
+ container,
96
139
  );
97
140
 
98
- @Controller('/api/users')
99
- class UserController {
100
- @GET('/:id')
101
- @Permission({ resource: 'user', action: 'read' })
102
- public getUser() {
103
- return { id: '123', name: 'Test User' };
141
+ expect(result).toBe('public data');
142
+ });
143
+
144
+ test('should execute method when user has permission', async () => {
145
+ class TestController {
146
+ @Permission({ resource: 'users', action: 'read' })
147
+ public getUsers(): string {
148
+ return 'users data';
104
149
  }
105
150
  }
106
151
 
107
- app.registerController(UserController);
108
- await app.listen();
152
+ const controller = new TestController();
109
153
 
110
- const response = await fetch(`http://localhost:${port}/api/users/123`, {
111
- headers: { 'X-User-Id': 'user1' },
154
+ // 创建带有用户 ID header 的上下文
155
+ const request = new Request('http://localhost/users', {
156
+ headers: {
157
+ 'X-User-Id': 'admin',
158
+ },
112
159
  });
160
+ const context = new Context(request, container);
113
161
 
114
- expect(response.status).toBe(403);
162
+ const result = await interceptor.execute(
163
+ controller,
164
+ 'getUsers',
165
+ controller.getUsers.bind(controller),
166
+ [],
167
+ container,
168
+ context,
169
+ );
170
+
171
+ expect(result).toBe('users data');
115
172
  });
116
173
 
117
- test('should allow anonymous access when allowAnonymous is true', async () => {
118
- @Controller('/api/public')
119
- class PublicController {
120
- @GET('/')
121
- @Permission({
122
- resource: 'public',
123
- action: 'read',
124
- allowAnonymous: true,
125
- })
126
- public getPublicData() {
127
- return { data: 'public' };
174
+ test('should throw ForbiddenException when user lacks permission', async () => {
175
+ class TestController {
176
+ @Permission({ resource: 'admin', action: 'delete' })
177
+ public deleteAdmin(): string {
178
+ return 'deleted';
128
179
  }
129
180
  }
130
181
 
131
- app.registerController(PublicController);
132
- await app.listen();
182
+ const controller = new TestController();
133
183
 
134
- // 没有提供用户 ID
135
- const response = await fetch(`http://localhost:${port}/api/public`);
184
+ const request = new Request('http://localhost/admin', {
185
+ headers: {
186
+ 'X-User-Id': 'user1',
187
+ },
188
+ });
189
+ const context = new Context(request, container);
136
190
 
137
- expect(response.status).toBe(200);
138
- const data = await response.json();
139
- expect(data.data).toBe('public');
191
+ await expect(
192
+ interceptor.execute(
193
+ controller,
194
+ 'deleteAdmin',
195
+ controller.deleteAdmin.bind(controller),
196
+ [],
197
+ container,
198
+ context,
199
+ ),
200
+ ).rejects.toThrow();
140
201
  });
141
202
 
142
- test('should work without permission decorator', async () => {
143
- @Controller('/api/test')
203
+ test('should throw error when PermissionService not registered', async () => {
204
+ const emptyContainer = new Container();
205
+
144
206
  class TestController {
145
- @GET('/')
146
- public getData() {
147
- return { data: 'no-permission-check' };
207
+ @Permission({ resource: 'users', action: 'read' })
208
+ public getUsers(): string {
209
+ return 'users';
148
210
  }
149
211
  }
150
212
 
151
- app.registerController(TestController);
152
- await app.listen();
213
+ const controller = new TestController();
214
+ const request = new Request('http://localhost/users');
215
+ const context = new Context(request, emptyContainer);
216
+
217
+ await expect(
218
+ interceptor.execute(
219
+ controller,
220
+ 'getUsers',
221
+ controller.getUsers.bind(controller),
222
+ [],
223
+ emptyContainer,
224
+ context,
225
+ ),
226
+ ).rejects.toThrow('PermissionService not found');
227
+ });
228
+
229
+ test('should return null userId when no context provided', async () => {
230
+ class TestController {
231
+ @Permission({ resource: 'users', action: 'read' })
232
+ public getUsers(): string {
233
+ return 'users';
234
+ }
235
+ }
153
236
 
154
- const response = await fetch(`http://localhost:${port}/api/test`);
237
+ const controller = new TestController();
155
238
 
156
- expect(response.status).toBe(200);
157
- const data = await response.json();
158
- expect(data.data).toBe('no-permission-check');
239
+ // 没有 context,userId 为 null,应该被拒绝
240
+ await expect(
241
+ interceptor.execute(
242
+ controller,
243
+ 'getUsers',
244
+ controller.getUsers.bind(controller),
245
+ [],
246
+ container,
247
+ ),
248
+ ).rejects.toThrow();
159
249
  });
160
250
 
161
- test('should throw error when PermissionService not registered', async () => {
162
- @Controller('/api/users')
163
- class UserController {
164
- @GET('/:id')
165
- @Permission({ resource: 'user', action: 'read' })
166
- public getUser() {
167
- return { id: '123' };
251
+ test('should handle Authorization Bearer header', async () => {
252
+ class TestController {
253
+ @Permission({ resource: 'users', action: 'read' })
254
+ public getUsers(): string {
255
+ return 'users';
168
256
  }
169
257
  }
170
258
 
171
- app.registerController(UserController);
172
- await app.listen();
259
+ const controller = new TestController();
173
260
 
174
- const response = await fetch(`http://localhost:${port}/api/users/123`, {
175
- headers: { 'X-User-Id': 'user1' },
261
+ // 带有 Authorization header 但没有 X-User-Id
262
+ const request = new Request('http://localhost/users', {
263
+ headers: {
264
+ 'Authorization': 'Bearer some-token',
265
+ 'X-User-Id': 'admin',
266
+ },
176
267
  });
268
+ const context = new Context(request, container);
177
269
 
178
- // 应该返回 500 错误(PermissionService 未注册)
179
- expect(response.status).toBe(500);
270
+ const result = await interceptor.execute(
271
+ controller,
272
+ 'getUsers',
273
+ controller.getUsers.bind(controller),
274
+ [],
275
+ container,
276
+ context,
277
+ );
278
+
279
+ expect(result).toBe('users');
180
280
  });
181
281
  });
182
-