@dangao/bun-server 1.0.3 → 1.1.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.
- package/dist/controller/controller.d.ts +1 -1
- package/dist/controller/controller.d.ts.map +1 -1
- package/dist/core/application.d.ts.map +1 -1
- package/dist/database/database-extension.d.ts.map +1 -1
- package/dist/database/database-module.d.ts.map +1 -1
- package/dist/database/orm/transaction-decorator.d.ts +1 -0
- package/dist/database/orm/transaction-decorator.d.ts.map +1 -1
- package/dist/database/orm/transaction-interceptor.d.ts +12 -3
- package/dist/database/orm/transaction-interceptor.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +678 -310
- package/dist/interceptor/base-interceptor.d.ts +94 -0
- package/dist/interceptor/base-interceptor.d.ts.map +1 -0
- package/dist/interceptor/builtin/cache-interceptor.d.ts +69 -0
- package/dist/interceptor/builtin/cache-interceptor.d.ts.map +1 -0
- package/dist/interceptor/builtin/index.d.ts +4 -0
- package/dist/interceptor/builtin/index.d.ts.map +1 -0
- package/dist/interceptor/builtin/log-interceptor.d.ts +56 -0
- package/dist/interceptor/builtin/log-interceptor.d.ts.map +1 -0
- package/dist/interceptor/builtin/permission-interceptor.d.ts +70 -0
- package/dist/interceptor/builtin/permission-interceptor.d.ts.map +1 -0
- package/dist/interceptor/index.d.ts +7 -0
- package/dist/interceptor/index.d.ts.map +1 -0
- package/dist/interceptor/interceptor-chain.d.ts +22 -0
- package/dist/interceptor/interceptor-chain.d.ts.map +1 -0
- package/dist/interceptor/interceptor-registry.d.ts +59 -0
- package/dist/interceptor/interceptor-registry.d.ts.map +1 -0
- package/dist/interceptor/metadata.d.ts +12 -0
- package/dist/interceptor/metadata.d.ts.map +1 -0
- package/dist/interceptor/types.d.ts +42 -0
- package/dist/interceptor/types.d.ts.map +1 -0
- package/dist/middleware/decorators.d.ts +2 -1
- package/dist/middleware/decorators.d.ts.map +1 -1
- package/dist/router/decorators.d.ts.map +1 -1
- package/dist/router/registry.d.ts +2 -1
- package/dist/router/registry.d.ts.map +1 -1
- package/dist/router/route.d.ts +3 -2
- package/dist/router/route.d.ts.map +1 -1
- package/dist/router/router.d.ts +2 -1
- package/dist/router/router.d.ts.map +1 -1
- package/dist/websocket/decorators.d.ts +2 -1
- package/dist/websocket/decorators.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/controller/controller.ts +41 -14
- package/src/core/application.ts +7 -1
- package/src/database/database-extension.ts +23 -2
- package/src/database/database-module.ts +6 -0
- package/src/database/orm/transaction-decorator.ts +33 -2
- package/src/database/orm/transaction-interceptor.ts +31 -11
- package/src/index.ts +22 -0
- package/src/interceptor/base-interceptor.ts +203 -0
- package/src/interceptor/builtin/cache-interceptor.ts +169 -0
- package/src/interceptor/builtin/index.ts +28 -0
- package/src/interceptor/builtin/log-interceptor.ts +178 -0
- package/src/interceptor/builtin/permission-interceptor.ts +173 -0
- package/src/interceptor/index.ts +26 -0
- package/src/interceptor/interceptor-chain.ts +79 -0
- package/src/interceptor/interceptor-registry.ts +132 -0
- package/src/interceptor/metadata.ts +40 -0
- package/src/interceptor/types.ts +52 -0
- package/src/middleware/decorators.ts +2 -1
- package/src/router/decorators.ts +44 -43
- package/src/router/registry.ts +2 -1
- package/src/router/route.ts +3 -2
- package/src/router/router.ts +2 -1
- package/src/websocket/decorators.ts +3 -1
- package/tests/controller/path-combination.test.ts +207 -0
- package/tests/interceptor/builtin/cache-interceptor.test.ts +137 -0
- package/tests/interceptor/builtin/permission-interceptor.test.ts +182 -0
- package/tests/interceptor/interceptor-advanced-integration.test.ts +592 -0
- package/tests/interceptor/interceptor-arg-modification.test.ts +76 -0
- package/tests/interceptor/interceptor-chain.test.ts +199 -0
- package/tests/interceptor/interceptor-integration.test.ts +230 -0
- package/tests/interceptor/interceptor-registry.test.ts +200 -0
- package/tests/interceptor/perf/interceptor-performance.test.ts +341 -0
- package/tests/router/decorators.test.ts +13 -15
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { describe, expect, test } from 'bun:test';
|
|
3
|
+
import { InterceptorChain } from '../../src/interceptor';
|
|
4
|
+
import type { Interceptor } from '../../src/interceptor';
|
|
5
|
+
import { Container } from '../../src/di/container';
|
|
6
|
+
import { Context } from '../../src/core/context';
|
|
7
|
+
|
|
8
|
+
describe('InterceptorChain', () => {
|
|
9
|
+
test('should execute method directly when no interceptors', async () => {
|
|
10
|
+
const container = new Container();
|
|
11
|
+
const target = {};
|
|
12
|
+
const propertyKey = 'testMethod';
|
|
13
|
+
const originalMethod = () => 'result';
|
|
14
|
+
|
|
15
|
+
const result = await InterceptorChain.execute(
|
|
16
|
+
[],
|
|
17
|
+
target,
|
|
18
|
+
propertyKey,
|
|
19
|
+
originalMethod,
|
|
20
|
+
[],
|
|
21
|
+
container,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
expect(result).toBe('result');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('should execute single interceptor', async () => {
|
|
28
|
+
const container = new Container();
|
|
29
|
+
const target = {};
|
|
30
|
+
const propertyKey = 'testMethod';
|
|
31
|
+
const originalMethod = () => 'result';
|
|
32
|
+
let executed = false;
|
|
33
|
+
|
|
34
|
+
const interceptor: Interceptor = {
|
|
35
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
36
|
+
executed = true;
|
|
37
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const result = await InterceptorChain.execute(
|
|
42
|
+
[interceptor],
|
|
43
|
+
target,
|
|
44
|
+
propertyKey,
|
|
45
|
+
originalMethod,
|
|
46
|
+
[],
|
|
47
|
+
container,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(executed).toBe(true);
|
|
51
|
+
expect(result).toBe('result');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('should execute multiple interceptors in order', async () => {
|
|
55
|
+
const container = new Container();
|
|
56
|
+
const target = {};
|
|
57
|
+
const propertyKey = 'testMethod';
|
|
58
|
+
const originalMethod = () => 'result';
|
|
59
|
+
const executionOrder: string[] = [];
|
|
60
|
+
|
|
61
|
+
const interceptor1: Interceptor = {
|
|
62
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
63
|
+
executionOrder.push('interceptor1-before');
|
|
64
|
+
const result = await Promise.resolve(originalMethod.apply(target, args));
|
|
65
|
+
executionOrder.push('interceptor1-after');
|
|
66
|
+
return result;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const interceptor2: Interceptor = {
|
|
71
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
72
|
+
executionOrder.push('interceptor2-before');
|
|
73
|
+
const result = await Promise.resolve(originalMethod.apply(target, args));
|
|
74
|
+
executionOrder.push('interceptor2-after');
|
|
75
|
+
return result;
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const result = await InterceptorChain.execute(
|
|
80
|
+
[interceptor1, interceptor2],
|
|
81
|
+
target,
|
|
82
|
+
propertyKey,
|
|
83
|
+
originalMethod,
|
|
84
|
+
[],
|
|
85
|
+
container,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
expect(result).toBe('result');
|
|
89
|
+
// 执行顺序:interceptor1 -> interceptor2 -> method -> interceptor2 -> interceptor1
|
|
90
|
+
expect(executionOrder).toEqual([
|
|
91
|
+
'interceptor1-before',
|
|
92
|
+
'interceptor2-before',
|
|
93
|
+
'interceptor2-after',
|
|
94
|
+
'interceptor1-after',
|
|
95
|
+
]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('should handle async method', async () => {
|
|
99
|
+
const container = new Container();
|
|
100
|
+
const target = {};
|
|
101
|
+
const propertyKey = 'testMethod';
|
|
102
|
+
const originalMethod = async () => {
|
|
103
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
104
|
+
return 'async-result';
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const interceptor: Interceptor = {
|
|
108
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
109
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const result = await InterceptorChain.execute(
|
|
114
|
+
[interceptor],
|
|
115
|
+
target,
|
|
116
|
+
propertyKey,
|
|
117
|
+
originalMethod,
|
|
118
|
+
[],
|
|
119
|
+
container,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
expect(result).toBe('async-result');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('should propagate errors', async () => {
|
|
126
|
+
const container = new Container();
|
|
127
|
+
const target = {};
|
|
128
|
+
const propertyKey = 'testMethod';
|
|
129
|
+
const originalMethod = () => {
|
|
130
|
+
throw new Error('test error');
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const interceptor: Interceptor = {
|
|
134
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
135
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
await expect(
|
|
140
|
+
InterceptorChain.execute([interceptor], target, propertyKey, originalMethod, [], container),
|
|
141
|
+
).rejects.toThrow('test error');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('should pass context to interceptors', async () => {
|
|
145
|
+
const container = new Container();
|
|
146
|
+
const target = {};
|
|
147
|
+
const propertyKey = 'testMethod';
|
|
148
|
+
const originalMethod = () => 'result';
|
|
149
|
+
const context = new Context(new Request('http://localhost/test'));
|
|
150
|
+
let receivedContext: Context | undefined;
|
|
151
|
+
|
|
152
|
+
const interceptor: Interceptor = {
|
|
153
|
+
async execute(target, propertyKey, originalMethod, args, container, ctx) {
|
|
154
|
+
receivedContext = ctx;
|
|
155
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
await InterceptorChain.execute(
|
|
160
|
+
[interceptor],
|
|
161
|
+
target,
|
|
162
|
+
propertyKey,
|
|
163
|
+
originalMethod,
|
|
164
|
+
[],
|
|
165
|
+
container,
|
|
166
|
+
context,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
expect(receivedContext).toBe(context);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('should pass args to interceptors', async () => {
|
|
173
|
+
const container = new Container();
|
|
174
|
+
const target = {};
|
|
175
|
+
const propertyKey = 'testMethod';
|
|
176
|
+
const originalMethod = (a: string, b: number) => `${a}-${b}`;
|
|
177
|
+
let receivedArgs: unknown[] = [];
|
|
178
|
+
|
|
179
|
+
const interceptor: Interceptor = {
|
|
180
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
181
|
+
receivedArgs = args;
|
|
182
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const result = await InterceptorChain.execute(
|
|
187
|
+
[interceptor],
|
|
188
|
+
target,
|
|
189
|
+
propertyKey,
|
|
190
|
+
originalMethod,
|
|
191
|
+
['test', 123],
|
|
192
|
+
container,
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
expect(receivedArgs).toEqual(['test', 123]);
|
|
196
|
+
expect(result).toBe('test-123');
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
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';
|
|
7
|
+
import {
|
|
8
|
+
InterceptorRegistry,
|
|
9
|
+
INTERCEPTOR_REGISTRY_TOKEN,
|
|
10
|
+
InterceptorChain,
|
|
11
|
+
BaseInterceptor,
|
|
12
|
+
} from '../../src/interceptor';
|
|
13
|
+
import type { Interceptor } from '../../src/interceptor';
|
|
14
|
+
import { getTestPort } from '../utils/test-port';
|
|
15
|
+
|
|
16
|
+
describe('Interceptor Integration', () => {
|
|
17
|
+
let app: Application;
|
|
18
|
+
let port: number;
|
|
19
|
+
let interceptorRegistry: InterceptorRegistry;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
port = getTestPort();
|
|
23
|
+
app = new Application({ port });
|
|
24
|
+
interceptorRegistry = app.getContainer().resolve<InterceptorRegistry>(
|
|
25
|
+
INTERCEPTOR_REGISTRY_TOKEN,
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(async () => {
|
|
30
|
+
if (app) {
|
|
31
|
+
await app.stop();
|
|
32
|
+
}
|
|
33
|
+
RouteRegistry.getInstance().clear();
|
|
34
|
+
ControllerRegistry.getInstance().clear();
|
|
35
|
+
interceptorRegistry.clear();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('should execute custom interceptor', async () => {
|
|
39
|
+
const METADATA_KEY = Symbol('test:custom');
|
|
40
|
+
let interceptorExecuted = false;
|
|
41
|
+
|
|
42
|
+
// 创建自定义装饰器
|
|
43
|
+
function CustomDecorator(): MethodDecorator {
|
|
44
|
+
return (target, propertyKey, descriptor) => {
|
|
45
|
+
Reflect.defineMetadata(METADATA_KEY, true, target, propertyKey);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 创建拦截器
|
|
50
|
+
const interceptor: Interceptor = {
|
|
51
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
52
|
+
interceptorExecuted = true;
|
|
53
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// 注册拦截器
|
|
58
|
+
interceptorRegistry.register(METADATA_KEY, interceptor);
|
|
59
|
+
|
|
60
|
+
// 创建控制器
|
|
61
|
+
@Controller('/api/test')
|
|
62
|
+
class TestController {
|
|
63
|
+
@GET('/')
|
|
64
|
+
@CustomDecorator()
|
|
65
|
+
public test() {
|
|
66
|
+
return { message: 'test' };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
app.registerController(TestController);
|
|
71
|
+
await app.listen();
|
|
72
|
+
|
|
73
|
+
const response = await fetch(`http://localhost:${port}/api/test`);
|
|
74
|
+
expect(response.status).toBe(200);
|
|
75
|
+
const data = await response.json();
|
|
76
|
+
expect(data.message).toBe('test');
|
|
77
|
+
expect(interceptorExecuted).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('should execute multiple interceptors in chain', async () => {
|
|
81
|
+
const METADATA_KEY_1 = Symbol('test:custom:1');
|
|
82
|
+
const METADATA_KEY_2 = Symbol('test:custom:2');
|
|
83
|
+
const executionOrder: string[] = [];
|
|
84
|
+
|
|
85
|
+
// 创建自定义装饰器
|
|
86
|
+
function CustomDecorator1(): MethodDecorator {
|
|
87
|
+
return (target, propertyKey, descriptor) => {
|
|
88
|
+
Reflect.defineMetadata(METADATA_KEY_1, true, target, propertyKey);
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function CustomDecorator2(): MethodDecorator {
|
|
93
|
+
return (target, propertyKey, descriptor) => {
|
|
94
|
+
Reflect.defineMetadata(METADATA_KEY_2, true, target, propertyKey);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 创建拦截器
|
|
99
|
+
const interceptor1: Interceptor = {
|
|
100
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
101
|
+
executionOrder.push('interceptor1');
|
|
102
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const interceptor2: Interceptor = {
|
|
107
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
108
|
+
executionOrder.push('interceptor2');
|
|
109
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// 注册拦截器(interceptor2 优先级更高)
|
|
114
|
+
interceptorRegistry.register(METADATA_KEY_1, interceptor1, 100);
|
|
115
|
+
interceptorRegistry.register(METADATA_KEY_2, interceptor2, 50);
|
|
116
|
+
|
|
117
|
+
// 创建控制器
|
|
118
|
+
@Controller('/api/test')
|
|
119
|
+
class TestController {
|
|
120
|
+
@GET('/')
|
|
121
|
+
@CustomDecorator1()
|
|
122
|
+
@CustomDecorator2()
|
|
123
|
+
public test() {
|
|
124
|
+
executionOrder.push('method');
|
|
125
|
+
return { message: 'test' };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
app.registerController(TestController);
|
|
130
|
+
await app.listen();
|
|
131
|
+
|
|
132
|
+
const response = await fetch(`http://localhost:${port}/api/test`);
|
|
133
|
+
expect(response.status).toBe(200);
|
|
134
|
+
const data = await response.json();
|
|
135
|
+
expect(data.message).toBe('test');
|
|
136
|
+
// 执行顺序:interceptor2 (50) -> interceptor1 (100) -> method
|
|
137
|
+
expect(executionOrder).toEqual(['interceptor2', 'interceptor1', 'method']);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('should work without interceptors (backward compatibility)', async () => {
|
|
141
|
+
@Controller('/api/test')
|
|
142
|
+
class TestController {
|
|
143
|
+
@GET('/')
|
|
144
|
+
public test() {
|
|
145
|
+
return { message: 'test' };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
app.registerController(TestController);
|
|
150
|
+
await app.listen();
|
|
151
|
+
|
|
152
|
+
const response = await fetch(`http://localhost:${port}/api/test`);
|
|
153
|
+
expect(response.status).toBe(200);
|
|
154
|
+
const data = await response.json();
|
|
155
|
+
expect(data.message).toBe('test');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('should handle interceptor modifying result', async () => {
|
|
159
|
+
const METADATA_KEY = Symbol('test:modify');
|
|
160
|
+
|
|
161
|
+
function CustomDecorator(): MethodDecorator {
|
|
162
|
+
return (target, propertyKey, descriptor) => {
|
|
163
|
+
Reflect.defineMetadata(METADATA_KEY, true, target, propertyKey);
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const interceptor: Interceptor = {
|
|
168
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
169
|
+
const result = await Promise.resolve(originalMethod.apply(target, args));
|
|
170
|
+
// 修改结果
|
|
171
|
+
return { ...result, modified: true };
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
interceptorRegistry.register(METADATA_KEY, interceptor);
|
|
176
|
+
|
|
177
|
+
@Controller('/api/test')
|
|
178
|
+
class TestController {
|
|
179
|
+
@GET('/')
|
|
180
|
+
@CustomDecorator()
|
|
181
|
+
public test() {
|
|
182
|
+
return { message: 'test' };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
app.registerController(TestController);
|
|
187
|
+
await app.listen();
|
|
188
|
+
|
|
189
|
+
const response = await fetch(`http://localhost:${port}/api/test`);
|
|
190
|
+
expect(response.status).toBe(200);
|
|
191
|
+
const data = await response.json();
|
|
192
|
+
expect(data.message).toBe('test');
|
|
193
|
+
expect(data.modified).toBe(true);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('should handle interceptor error', async () => {
|
|
197
|
+
const METADATA_KEY = Symbol('test:error');
|
|
198
|
+
|
|
199
|
+
function CustomDecorator(): MethodDecorator {
|
|
200
|
+
return (target, propertyKey, descriptor) => {
|
|
201
|
+
Reflect.defineMetadata(METADATA_KEY, true, target, propertyKey);
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const interceptor: Interceptor = {
|
|
206
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
207
|
+
throw new Error('Interceptor error');
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
interceptorRegistry.register(METADATA_KEY, interceptor);
|
|
212
|
+
|
|
213
|
+
@Controller('/api/test')
|
|
214
|
+
class TestController {
|
|
215
|
+
@GET('/')
|
|
216
|
+
@CustomDecorator()
|
|
217
|
+
public test() {
|
|
218
|
+
return { message: 'test' };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
app.registerController(TestController);
|
|
223
|
+
await app.listen();
|
|
224
|
+
|
|
225
|
+
const response = await fetch(`http://localhost:${port}/api/test`);
|
|
226
|
+
// 错误应该被全局错误处理器处理
|
|
227
|
+
expect(response.status).toBe(500);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { describe, expect, test, beforeEach } from 'bun:test';
|
|
3
|
+
import { InterceptorRegistry, INTERCEPTOR_REGISTRY_TOKEN } from '../../src/interceptor';
|
|
4
|
+
import type { Interceptor } from '../../src/interceptor';
|
|
5
|
+
import type { Container } from '../../src/di/container';
|
|
6
|
+
import type { Context } from '../../src/core/context';
|
|
7
|
+
|
|
8
|
+
describe('InterceptorRegistry', () => {
|
|
9
|
+
let registry: InterceptorRegistry;
|
|
10
|
+
const METADATA_KEY_1 = Symbol('test:metadata:1');
|
|
11
|
+
const METADATA_KEY_2 = Symbol('test:metadata:2');
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
registry = new InterceptorRegistry();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('should register interceptor', () => {
|
|
18
|
+
const interceptor: Interceptor = {
|
|
19
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
20
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
registry.register(METADATA_KEY_1, interceptor);
|
|
25
|
+
expect(registry.hasInterceptor(METADATA_KEY_1)).toBe(true);
|
|
26
|
+
expect(registry.count(METADATA_KEY_1)).toBe(1);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('should register multiple interceptors with same metadata key', () => {
|
|
30
|
+
const interceptor1: Interceptor = {
|
|
31
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
32
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
const interceptor2: Interceptor = {
|
|
36
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
37
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
registry.register(METADATA_KEY_1, interceptor1, 100);
|
|
42
|
+
registry.register(METADATA_KEY_1, interceptor2, 50);
|
|
43
|
+
|
|
44
|
+
const interceptors = registry.getInterceptors(METADATA_KEY_1);
|
|
45
|
+
expect(interceptors.length).toBe(2);
|
|
46
|
+
// 应该按优先级排序(50 < 100,所以 interceptor2 在前)
|
|
47
|
+
expect(interceptors[0]).toBe(interceptor2);
|
|
48
|
+
expect(interceptors[1]).toBe(interceptor1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('should not register duplicate interceptor', () => {
|
|
52
|
+
const interceptor: Interceptor = {
|
|
53
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
54
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
registry.register(METADATA_KEY_1, interceptor);
|
|
59
|
+
registry.register(METADATA_KEY_1, interceptor); // 重复注册
|
|
60
|
+
|
|
61
|
+
expect(registry.count(METADATA_KEY_1)).toBe(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('should get interceptors by metadata key', () => {
|
|
65
|
+
const interceptor1: Interceptor = {
|
|
66
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
67
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
const interceptor2: Interceptor = {
|
|
71
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
72
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
registry.register(METADATA_KEY_1, interceptor1);
|
|
77
|
+
registry.register(METADATA_KEY_2, interceptor2);
|
|
78
|
+
|
|
79
|
+
const interceptors1 = registry.getInterceptors(METADATA_KEY_1);
|
|
80
|
+
const interceptors2 = registry.getInterceptors(METADATA_KEY_2);
|
|
81
|
+
|
|
82
|
+
expect(interceptors1.length).toBe(1);
|
|
83
|
+
expect(interceptors1[0]).toBe(interceptor1);
|
|
84
|
+
expect(interceptors2.length).toBe(1);
|
|
85
|
+
expect(interceptors2[0]).toBe(interceptor2);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('should return empty array for non-existent metadata key', () => {
|
|
89
|
+
const interceptors = registry.getInterceptors(METADATA_KEY_1);
|
|
90
|
+
expect(interceptors).toEqual([]);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('should check if interceptor exists', () => {
|
|
94
|
+
const interceptor: Interceptor = {
|
|
95
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
96
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
expect(registry.hasInterceptor(METADATA_KEY_1)).toBe(false);
|
|
101
|
+
registry.register(METADATA_KEY_1, interceptor);
|
|
102
|
+
expect(registry.hasInterceptor(METADATA_KEY_1)).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('should clear all interceptors', () => {
|
|
106
|
+
const interceptor1: Interceptor = {
|
|
107
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
108
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
const interceptor2: Interceptor = {
|
|
112
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
113
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
registry.register(METADATA_KEY_1, interceptor1);
|
|
118
|
+
registry.register(METADATA_KEY_2, interceptor2);
|
|
119
|
+
|
|
120
|
+
expect(registry.count()).toBe(2);
|
|
121
|
+
registry.clear();
|
|
122
|
+
expect(registry.count()).toBe(0);
|
|
123
|
+
expect(registry.hasInterceptor(METADATA_KEY_1)).toBe(false);
|
|
124
|
+
expect(registry.hasInterceptor(METADATA_KEY_2)).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('should remove interceptors by metadata key', () => {
|
|
128
|
+
const interceptor1: Interceptor = {
|
|
129
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
130
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
const interceptor2: Interceptor = {
|
|
134
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
135
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
registry.register(METADATA_KEY_1, interceptor1);
|
|
140
|
+
registry.register(METADATA_KEY_2, interceptor2);
|
|
141
|
+
|
|
142
|
+
expect(registry.count()).toBe(2);
|
|
143
|
+
registry.remove(METADATA_KEY_1);
|
|
144
|
+
expect(registry.count()).toBe(1);
|
|
145
|
+
expect(registry.hasInterceptor(METADATA_KEY_1)).toBe(false);
|
|
146
|
+
expect(registry.hasInterceptor(METADATA_KEY_2)).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('should sort interceptors by priority', () => {
|
|
150
|
+
const interceptor1: Interceptor = {
|
|
151
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
152
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
const interceptor2: Interceptor = {
|
|
156
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
157
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
const interceptor3: Interceptor = {
|
|
161
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
162
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// 注册顺序:1(100), 2(50), 3(75)
|
|
167
|
+
registry.register(METADATA_KEY_1, interceptor1, 100);
|
|
168
|
+
registry.register(METADATA_KEY_1, interceptor2, 50);
|
|
169
|
+
registry.register(METADATA_KEY_1, interceptor3, 75);
|
|
170
|
+
|
|
171
|
+
const interceptors = registry.getInterceptors(METADATA_KEY_1);
|
|
172
|
+
expect(interceptors.length).toBe(3);
|
|
173
|
+
// 应该按优先级排序:50 < 75 < 100
|
|
174
|
+
expect(interceptors[0]).toBe(interceptor2); // 50
|
|
175
|
+
expect(interceptors[1]).toBe(interceptor3); // 75
|
|
176
|
+
expect(interceptors[2]).toBe(interceptor1); // 100
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('should get all metadata keys', () => {
|
|
180
|
+
const interceptor1: Interceptor = {
|
|
181
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
182
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
const interceptor2: Interceptor = {
|
|
186
|
+
async execute(target, propertyKey, originalMethod, args, container, context) {
|
|
187
|
+
return await Promise.resolve(originalMethod.apply(target, args));
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
registry.register(METADATA_KEY_1, interceptor1);
|
|
192
|
+
registry.register(METADATA_KEY_2, interceptor2);
|
|
193
|
+
|
|
194
|
+
const keys = Array.from(registry.getAllMetadataKeys());
|
|
195
|
+
expect(keys.length).toBe(2);
|
|
196
|
+
expect(keys).toContain(METADATA_KEY_1);
|
|
197
|
+
expect(keys).toContain(METADATA_KEY_2);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|