@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
|
@@ -1,182 +1,281 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach } from 'bun:test';
|
|
1
2
|
import 'reflect-metadata';
|
|
2
|
-
|
|
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
|
-
|
|
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('
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
|
|
36
|
+
const metadata = getPermissionMetadata(TestController.prototype, 'getPosts');
|
|
37
|
+
expect(metadata?.allowAnonymous).toBe(true);
|
|
39
38
|
});
|
|
40
39
|
|
|
41
|
-
test('should
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
+
mockPermissionService,
|
|
57
100
|
);
|
|
101
|
+
});
|
|
58
102
|
|
|
59
|
-
|
|
60
|
-
class
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
69
|
-
await app.listen();
|
|
110
|
+
const controller = new TestController();
|
|
70
111
|
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
112
|
+
const result = await interceptor.execute(
|
|
113
|
+
controller,
|
|
114
|
+
'normalMethod',
|
|
115
|
+
controller.normalMethod.bind(controller),
|
|
116
|
+
[],
|
|
117
|
+
container,
|
|
118
|
+
);
|
|
74
119
|
|
|
75
|
-
expect(
|
|
76
|
-
const data = await response.json();
|
|
77
|
-
expect(data.id).toBe('123');
|
|
120
|
+
expect(result).toBe('executed');
|
|
78
121
|
});
|
|
79
122
|
|
|
80
|
-
test('should
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
public
|
|
84
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
108
|
-
await app.listen();
|
|
152
|
+
const controller = new TestController();
|
|
109
153
|
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
132
|
-
await app.listen();
|
|
182
|
+
const controller = new TestController();
|
|
133
183
|
|
|
134
|
-
|
|
135
|
-
|
|
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(
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
143
|
-
|
|
203
|
+
test('should throw error when PermissionService not registered', async () => {
|
|
204
|
+
const emptyContainer = new Container();
|
|
205
|
+
|
|
144
206
|
class TestController {
|
|
145
|
-
@
|
|
146
|
-
public
|
|
147
|
-
return
|
|
207
|
+
@Permission({ resource: 'users', action: 'read' })
|
|
208
|
+
public getUsers(): string {
|
|
209
|
+
return 'users';
|
|
148
210
|
}
|
|
149
211
|
}
|
|
150
212
|
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
237
|
+
const controller = new TestController();
|
|
155
238
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
172
|
-
await app.listen();
|
|
259
|
+
const controller = new TestController();
|
|
173
260
|
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
179
|
-
|
|
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
|
-
|