@dangao/bun-server 1.3.0 → 1.5.0
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/README.md +15 -0
- package/dist/config/config-module.d.ts +17 -0
- package/dist/config/config-module.d.ts.map +1 -1
- package/dist/config/service.d.ts +18 -1
- package/dist/config/service.d.ts.map +1 -1
- package/dist/config/types.d.ts +25 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/controller/controller.d.ts +5 -0
- package/dist/controller/controller.d.ts.map +1 -1
- package/dist/core/application.d.ts +42 -1
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/context.d.ts +1 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/server.d.ts +33 -1
- package/dist/core/server.d.ts.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6510 -4164
- package/dist/microservice/config-center/config-center-module.d.ts +43 -0
- package/dist/microservice/config-center/config-center-module.d.ts.map +1 -0
- package/dist/microservice/config-center/decorators.d.ts +58 -0
- package/dist/microservice/config-center/decorators.d.ts.map +1 -0
- package/dist/microservice/config-center/index.d.ts +9 -0
- package/dist/microservice/config-center/index.d.ts.map +1 -0
- package/dist/microservice/config-center/nacos-config-center.d.ts +37 -0
- package/dist/microservice/config-center/nacos-config-center.d.ts.map +1 -0
- package/dist/microservice/config-center/nacos-decorators.d.ts +24 -0
- package/dist/microservice/config-center/nacos-decorators.d.ts.map +1 -0
- package/dist/microservice/config-center/types.d.ts +63 -0
- package/dist/microservice/config-center/types.d.ts.map +1 -0
- package/dist/microservice/governance/circuit-breaker.d.ts +54 -0
- package/dist/microservice/governance/circuit-breaker.d.ts.map +1 -0
- package/dist/microservice/governance/decorators.d.ts +51 -0
- package/dist/microservice/governance/decorators.d.ts.map +1 -0
- package/dist/microservice/governance/index.d.ts +9 -0
- package/dist/microservice/governance/index.d.ts.map +1 -0
- package/dist/microservice/governance/rate-limiter.d.ts +26 -0
- package/dist/microservice/governance/rate-limiter.d.ts.map +1 -0
- package/dist/microservice/governance/redis-rate-limiter.d.ts +76 -0
- package/dist/microservice/governance/redis-rate-limiter.d.ts.map +1 -0
- package/dist/microservice/governance/retry-strategy.d.ts +21 -0
- package/dist/microservice/governance/retry-strategy.d.ts.map +1 -0
- package/dist/microservice/governance/types.d.ts +212 -0
- package/dist/microservice/governance/types.d.ts.map +1 -0
- package/dist/microservice/index.d.ts +10 -0
- package/dist/microservice/index.d.ts.map +1 -0
- package/dist/microservice/monitoring/index.d.ts +4 -0
- package/dist/microservice/monitoring/index.d.ts.map +1 -0
- package/dist/microservice/monitoring/metrics-collector.d.ts +54 -0
- package/dist/microservice/monitoring/metrics-collector.d.ts.map +1 -0
- package/dist/microservice/monitoring/metrics-integration.d.ts +24 -0
- package/dist/microservice/monitoring/metrics-integration.d.ts.map +1 -0
- package/dist/microservice/monitoring/types.d.ts +99 -0
- package/dist/microservice/monitoring/types.d.ts.map +1 -0
- package/dist/microservice/service-client/call-decorators.d.ts +52 -0
- package/dist/microservice/service-client/call-decorators.d.ts.map +1 -0
- package/dist/microservice/service-client/decorators.d.ts +35 -0
- package/dist/microservice/service-client/decorators.d.ts.map +1 -0
- package/dist/microservice/service-client/index.d.ts +7 -0
- package/dist/microservice/service-client/index.d.ts.map +1 -0
- package/dist/microservice/service-client/interceptors.d.ts +96 -0
- package/dist/microservice/service-client/interceptors.d.ts.map +1 -0
- package/dist/microservice/service-client/load-balancer.d.ts +59 -0
- package/dist/microservice/service-client/load-balancer.d.ts.map +1 -0
- package/dist/microservice/service-client/service-client.d.ts +74 -0
- package/dist/microservice/service-client/service-client.d.ts.map +1 -0
- package/dist/microservice/service-client/types.d.ts +155 -0
- package/dist/microservice/service-client/types.d.ts.map +1 -0
- package/dist/microservice/service-registry/decorators.d.ts +84 -0
- package/dist/microservice/service-registry/decorators.d.ts.map +1 -0
- package/dist/microservice/service-registry/discovery-decorators.d.ts +58 -0
- package/dist/microservice/service-registry/discovery-decorators.d.ts.map +1 -0
- package/dist/microservice/service-registry/health-integration.d.ts +32 -0
- package/dist/microservice/service-registry/health-integration.d.ts.map +1 -0
- package/dist/microservice/service-registry/index.d.ts +10 -0
- package/dist/microservice/service-registry/index.d.ts.map +1 -0
- package/dist/microservice/service-registry/nacos-service-registry.d.ts +68 -0
- package/dist/microservice/service-registry/nacos-service-registry.d.ts.map +1 -0
- package/dist/microservice/service-registry/service-registry-module.d.ts +48 -0
- package/dist/microservice/service-registry/service-registry-module.d.ts.map +1 -0
- package/dist/microservice/service-registry/types.d.ts +121 -0
- package/dist/microservice/service-registry/types.d.ts.map +1 -0
- package/dist/microservice/tracing/collectors.d.ts +27 -0
- package/dist/microservice/tracing/collectors.d.ts.map +1 -0
- package/dist/microservice/tracing/index.d.ts +4 -0
- package/dist/microservice/tracing/index.d.ts.map +1 -0
- package/dist/microservice/tracing/tracer.d.ts +59 -0
- package/dist/microservice/tracing/tracer.d.ts.map +1 -0
- package/dist/microservice/tracing/types.d.ts +179 -0
- package/dist/microservice/tracing/types.d.ts.map +1 -0
- package/dist/request/request.d.ts +1 -0
- package/dist/request/request.d.ts.map +1 -1
- package/docs/microservice-config-center.md +258 -0
- package/docs/microservice-nacos.md +346 -0
- package/docs/microservice-service-registry.md +306 -0
- package/docs/microservice.md +680 -0
- package/docs/troubleshooting.md +41 -0
- package/docs/zh/troubleshooting.md +41 -0
- package/package.json +5 -4
- package/src/config/config-module.ts +210 -0
- package/src/config/service.ts +52 -1
- package/src/config/types.ts +31 -0
- package/src/controller/controller.ts +8 -0
- package/src/core/application.ts +189 -3
- package/src/core/context.ts +1 -0
- package/src/core/server.ts +128 -2
- package/src/index.ts +81 -0
- package/src/microservice/config-center/config-center-module.ts +98 -0
- package/src/microservice/config-center/decorators.ts +159 -0
- package/src/microservice/config-center/index.ts +13 -0
- package/src/microservice/config-center/nacos-config-center.ts +126 -0
- package/src/microservice/config-center/nacos-decorators.ts +34 -0
- package/src/microservice/config-center/types.ts +80 -0
- package/src/microservice/governance/circuit-breaker.ts +229 -0
- package/src/microservice/governance/decorators.ts +113 -0
- package/src/microservice/governance/index.ts +18 -0
- package/src/microservice/governance/rate-limiter.ts +72 -0
- package/src/microservice/governance/redis-rate-limiter.ts +154 -0
- package/src/microservice/governance/retry-strategy.ts +74 -0
- package/src/microservice/governance/types.ts +247 -0
- package/src/microservice/index.ts +12 -0
- package/src/microservice/monitoring/index.ts +8 -0
- package/src/microservice/monitoring/metrics-collector.ts +223 -0
- package/src/microservice/monitoring/metrics-integration.ts +154 -0
- package/src/microservice/monitoring/types.ts +118 -0
- package/src/microservice/service-client/call-decorators.ts +107 -0
- package/src/microservice/service-client/decorators.ts +87 -0
- package/src/microservice/service-client/index.ts +37 -0
- package/src/microservice/service-client/interceptors.ts +182 -0
- package/src/microservice/service-client/load-balancer.ts +205 -0
- package/src/microservice/service-client/service-client.ts +488 -0
- package/src/microservice/service-client/types.ts +186 -0
- package/src/microservice/service-registry/decorators.ts +238 -0
- package/src/microservice/service-registry/discovery-decorators.ts +156 -0
- package/src/microservice/service-registry/health-integration.ts +146 -0
- package/src/microservice/service-registry/index.ts +20 -0
- package/src/microservice/service-registry/nacos-service-registry.ts +259 -0
- package/src/microservice/service-registry/service-registry-module.ts +105 -0
- package/src/microservice/service-registry/types.ts +149 -0
- package/src/microservice/tracing/collectors.ts +50 -0
- package/src/microservice/tracing/index.ts +15 -0
- package/src/microservice/tracing/tracer.ts +293 -0
- package/src/microservice/tracing/types.ts +213 -0
- package/src/request/request.ts +1 -0
- package/tests/config/set-value-by-path.test.ts +53 -0
- package/tests/core/graceful-shutdown.test.ts +321 -0
- package/tests/microservice/config-center.test.ts +77 -0
- package/tests/microservice/governance.test.ts +157 -0
- package/tests/microservice/monitoring.test.ts +75 -0
- package/tests/microservice/service-client.test.ts +136 -0
- package/tests/microservice/service-registry.test.ts +80 -0
- package/tests/microservice/tracing.test.ts +143 -0
- package/tests/utils/test-port.ts +29 -19
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { describe, expect, test, afterEach, beforeEach } from 'bun:test';
|
|
2
|
+
import { Application } from '../../src/core/application';
|
|
3
|
+
import { Controller, ControllerRegistry } from '../../src/controller/controller';
|
|
4
|
+
import { GET } from '../../src/router/decorators';
|
|
5
|
+
import { Param } from '../../src/controller/decorators';
|
|
6
|
+
import { getTestPort } from '../utils/test-port';
|
|
7
|
+
import { RouteRegistry } from '../../src/router/registry';
|
|
8
|
+
|
|
9
|
+
describe('Graceful Shutdown', () => {
|
|
10
|
+
let app: Application;
|
|
11
|
+
let port: number;
|
|
12
|
+
|
|
13
|
+
beforeEach((done) => {
|
|
14
|
+
port = getTestPort();
|
|
15
|
+
done();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(async (done) => {
|
|
19
|
+
if (app) {
|
|
20
|
+
// 确保清理,使用 stop 而不是 gracefulShutdown 以避免测试间干扰
|
|
21
|
+
try {
|
|
22
|
+
const server = app.getServer();
|
|
23
|
+
if (server?.isRunning()) {
|
|
24
|
+
await app.stop();
|
|
25
|
+
// 等待一小段时间确保端口释放
|
|
26
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
27
|
+
}
|
|
28
|
+
} catch (error) {
|
|
29
|
+
// 忽略错误,确保清理完成
|
|
30
|
+
}
|
|
31
|
+
app = undefined as any;
|
|
32
|
+
}
|
|
33
|
+
RouteRegistry.getInstance().clear();
|
|
34
|
+
ControllerRegistry.getInstance().clear();
|
|
35
|
+
done();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('should reject new requests during shutdown', async () => {
|
|
39
|
+
@Controller('/api')
|
|
40
|
+
class TestController {
|
|
41
|
+
@GET('/test')
|
|
42
|
+
public test() {
|
|
43
|
+
return { message: 'ok' };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@GET('/slow')
|
|
47
|
+
public async slow() {
|
|
48
|
+
// 模拟慢请求,确保在停机过程中有活跃请求
|
|
49
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
50
|
+
return { message: 'completed' };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
app = new Application({ port, enableSignalHandlers: false });
|
|
55
|
+
app.registerController(TestController);
|
|
56
|
+
await app.listen();
|
|
57
|
+
|
|
58
|
+
const server = app.getServer();
|
|
59
|
+
expect(server).toBeDefined();
|
|
60
|
+
|
|
61
|
+
// 先发送一个慢请求,确保在停机过程中有活跃请求
|
|
62
|
+
const slowRequestPromise = fetch(`http://localhost:${port}/api/slow`);
|
|
63
|
+
|
|
64
|
+
// 等待请求开始处理
|
|
65
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
66
|
+
|
|
67
|
+
// 开始优雅停机(此时有活跃请求,服务器不会立即关闭)
|
|
68
|
+
const shutdownPromise = app.gracefulShutdown(5000);
|
|
69
|
+
|
|
70
|
+
// 等待一小段时间确保 shutdown 状态已设置
|
|
71
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
72
|
+
|
|
73
|
+
// 尝试发送新请求,应该被拒绝
|
|
74
|
+
const response = await fetch(`http://localhost:${port}/api/test`);
|
|
75
|
+
expect(response.status).toBe(503);
|
|
76
|
+
expect(await response.text()).toBe('Server is shutting down');
|
|
77
|
+
|
|
78
|
+
// 等待慢请求完成
|
|
79
|
+
await slowRequestPromise;
|
|
80
|
+
|
|
81
|
+
// 等待停机完成
|
|
82
|
+
await shutdownPromise;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('should wait for active requests to complete', async () => {
|
|
86
|
+
let requestCompleted = false;
|
|
87
|
+
|
|
88
|
+
@Controller('/api')
|
|
89
|
+
class TestController {
|
|
90
|
+
@GET('/slow')
|
|
91
|
+
public async slow() {
|
|
92
|
+
// 模拟慢请求
|
|
93
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
94
|
+
requestCompleted = true;
|
|
95
|
+
return { message: 'completed' };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
app = new Application({ port, enableSignalHandlers: false });
|
|
100
|
+
app.registerController(TestController);
|
|
101
|
+
await app.listen();
|
|
102
|
+
|
|
103
|
+
// 发送一个慢请求
|
|
104
|
+
const requestPromise = fetch(`http://localhost:${port}/api/slow`);
|
|
105
|
+
|
|
106
|
+
// 等待请求开始处理
|
|
107
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
108
|
+
|
|
109
|
+
// 开始优雅停机
|
|
110
|
+
const shutdownPromise = app.gracefulShutdown(5000);
|
|
111
|
+
|
|
112
|
+
// 等待请求完成
|
|
113
|
+
const response = await requestPromise;
|
|
114
|
+
expect(response.status).toBe(200);
|
|
115
|
+
const data = (await response.json()) as { message: string };
|
|
116
|
+
expect(data.message).toBe('completed');
|
|
117
|
+
expect(requestCompleted).toBe(true);
|
|
118
|
+
|
|
119
|
+
// 等待停机完成
|
|
120
|
+
await shutdownPromise;
|
|
121
|
+
|
|
122
|
+
const server = app.getServer();
|
|
123
|
+
expect(server?.isRunning()).toBe(false);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('should force shutdown after timeout', async () => {
|
|
127
|
+
@Controller('/api')
|
|
128
|
+
class TestController {
|
|
129
|
+
@GET('/very-slow')
|
|
130
|
+
public async verySlow() {
|
|
131
|
+
// 模拟非常慢的请求(超过超时时间)
|
|
132
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
133
|
+
return { message: 'completed' };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
app = new Application({ port, enableSignalHandlers: false });
|
|
138
|
+
app.registerController(TestController);
|
|
139
|
+
await app.listen();
|
|
140
|
+
|
|
141
|
+
// 发送一个非常慢的请求
|
|
142
|
+
const requestPromise = fetch(`http://localhost:${port}/api/very-slow`);
|
|
143
|
+
|
|
144
|
+
// 等待请求开始处理
|
|
145
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
146
|
+
|
|
147
|
+
// 开始优雅停机,设置较短的超时时间
|
|
148
|
+
const startTime = Date.now();
|
|
149
|
+
await app.gracefulShutdown(500);
|
|
150
|
+
const shutdownDuration = Date.now() - startTime;
|
|
151
|
+
|
|
152
|
+
// 应该在大约 500ms 后强制关闭(允许一些误差)
|
|
153
|
+
expect(shutdownDuration).toBeGreaterThanOrEqual(450);
|
|
154
|
+
expect(shutdownDuration).toBeLessThan(1000);
|
|
155
|
+
|
|
156
|
+
const server = app.getServer();
|
|
157
|
+
expect(server?.isRunning()).toBe(false);
|
|
158
|
+
|
|
159
|
+
// 请求可能仍在进行,但不应该影响服务器状态
|
|
160
|
+
try {
|
|
161
|
+
await requestPromise;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
// 请求可能失败,这是预期的
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('should handle multiple concurrent requests during shutdown', async () => {
|
|
168
|
+
const completedRequests: number[] = [];
|
|
169
|
+
|
|
170
|
+
@Controller('/api')
|
|
171
|
+
class TestController {
|
|
172
|
+
@GET('/concurrent/:id')
|
|
173
|
+
public async concurrent(@Param('id') id: string) {
|
|
174
|
+
// 模拟异步处理
|
|
175
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
176
|
+
completedRequests.push(Number.parseInt(id));
|
|
177
|
+
return { id, message: 'completed' };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
app = new Application({ port, enableSignalHandlers: false });
|
|
182
|
+
app.registerController(TestController);
|
|
183
|
+
await app.listen();
|
|
184
|
+
|
|
185
|
+
// 发送多个并发请求
|
|
186
|
+
const requests = Promise.all([
|
|
187
|
+
fetch(`http://localhost:${port}/api/concurrent/1`),
|
|
188
|
+
fetch(`http://localhost:${port}/api/concurrent/2`),
|
|
189
|
+
fetch(`http://localhost:${port}/api/concurrent/3`),
|
|
190
|
+
]);
|
|
191
|
+
|
|
192
|
+
// 等待请求开始处理
|
|
193
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
194
|
+
|
|
195
|
+
// 开始优雅停机
|
|
196
|
+
const shutdownPromise = app.gracefulShutdown(5000);
|
|
197
|
+
|
|
198
|
+
// 等待所有请求完成
|
|
199
|
+
const responses = await requests;
|
|
200
|
+
expect(responses.length).toBe(3);
|
|
201
|
+
|
|
202
|
+
for (const response of responses) {
|
|
203
|
+
expect(response.status).toBe(200);
|
|
204
|
+
const data = (await response.json()) as { message: string };
|
|
205
|
+
expect(data.message).toBe('completed');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 所有请求应该都完成了
|
|
209
|
+
expect(completedRequests.sort()).toEqual([1, 2, 3]);
|
|
210
|
+
|
|
211
|
+
// 等待停机完成
|
|
212
|
+
await shutdownPromise;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('should handle graceful shutdown with no active requests', async () => {
|
|
216
|
+
@Controller('/api')
|
|
217
|
+
class TestController {
|
|
218
|
+
@GET('/test')
|
|
219
|
+
public test() {
|
|
220
|
+
return { message: 'ok' };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
app = new Application({ port, enableSignalHandlers: false });
|
|
225
|
+
app.registerController(TestController);
|
|
226
|
+
await app.listen();
|
|
227
|
+
|
|
228
|
+
const server = app.getServer();
|
|
229
|
+
expect(server?.isRunning()).toBe(true);
|
|
230
|
+
|
|
231
|
+
// 没有活跃请求时,应该立即关闭
|
|
232
|
+
const startTime = Date.now();
|
|
233
|
+
await app.gracefulShutdown(5000);
|
|
234
|
+
const shutdownDuration = Date.now() - startTime;
|
|
235
|
+
|
|
236
|
+
// 应该几乎立即关闭(允许一些处理时间)
|
|
237
|
+
expect(shutdownDuration).toBeLessThan(100);
|
|
238
|
+
|
|
239
|
+
expect(server?.isRunning()).toBe(false);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('should track active requests correctly', async () => {
|
|
243
|
+
@Controller('/api')
|
|
244
|
+
class TestController {
|
|
245
|
+
@GET('/slow')
|
|
246
|
+
public async slow() {
|
|
247
|
+
// 使用慢请求确保在检查时请求仍在处理中
|
|
248
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
249
|
+
return { message: 'ok' };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
app = new Application({ port, enableSignalHandlers: false });
|
|
254
|
+
app.registerController(TestController);
|
|
255
|
+
await app.listen();
|
|
256
|
+
|
|
257
|
+
const server = app.getServer();
|
|
258
|
+
expect(server).toBeDefined();
|
|
259
|
+
|
|
260
|
+
// 发送慢请求
|
|
261
|
+
const requestPromise = fetch(`http://localhost:${port}/api/slow`);
|
|
262
|
+
|
|
263
|
+
// 等待请求开始处理(但不要等到完成)
|
|
264
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
265
|
+
|
|
266
|
+
// 检查活跃请求数(应该大于 0,因为请求还在处理中)
|
|
267
|
+
const activeRequests = server?.getActiveRequests() ?? 0;
|
|
268
|
+
expect(activeRequests).toBeGreaterThan(0);
|
|
269
|
+
|
|
270
|
+
// 等待请求完成
|
|
271
|
+
await requestPromise;
|
|
272
|
+
|
|
273
|
+
// 等待一小段时间确保计数更新
|
|
274
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
275
|
+
|
|
276
|
+
// 活跃请求数应该回到 0
|
|
277
|
+
const finalActiveRequests = server?.getActiveRequests() ?? 0;
|
|
278
|
+
expect(finalActiveRequests).toBe(0);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('should use custom graceful shutdown timeout', async () => {
|
|
282
|
+
@Controller('/api')
|
|
283
|
+
class TestController {
|
|
284
|
+
@GET('/slow')
|
|
285
|
+
public async slow() {
|
|
286
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
287
|
+
return { message: 'completed' };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
app = new Application({
|
|
292
|
+
port,
|
|
293
|
+
enableSignalHandlers: false,
|
|
294
|
+
gracefulShutdownTimeout: 2000,
|
|
295
|
+
});
|
|
296
|
+
app.registerController(TestController);
|
|
297
|
+
await app.listen();
|
|
298
|
+
|
|
299
|
+
// 发送请求
|
|
300
|
+
const requestPromise = fetch(`http://localhost:${port}/api/slow`);
|
|
301
|
+
|
|
302
|
+
// 等待请求开始处理
|
|
303
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
304
|
+
|
|
305
|
+
// 使用自定义超时时间(比配置的短)
|
|
306
|
+
const startTime = Date.now();
|
|
307
|
+
await app.gracefulShutdown(100);
|
|
308
|
+
const shutdownDuration = Date.now() - startTime;
|
|
309
|
+
|
|
310
|
+
// 应该在大约 100ms 后强制关闭
|
|
311
|
+
expect(shutdownDuration).toBeGreaterThanOrEqual(80);
|
|
312
|
+
expect(shutdownDuration).toBeLessThan(200);
|
|
313
|
+
|
|
314
|
+
// 等待请求完成(可能失败)
|
|
315
|
+
try {
|
|
316
|
+
await requestPromise;
|
|
317
|
+
} catch (error) {
|
|
318
|
+
// 请求可能失败,这是预期的
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach } from 'bun:test';
|
|
2
|
+
import 'reflect-metadata';
|
|
3
|
+
import { ConfigCenterModule, CONFIG_CENTER_TOKEN, type ConfigCenter } from '../../src/microservice/config-center';
|
|
4
|
+
import { Container } from '../../src/di/container';
|
|
5
|
+
import { ModuleRegistry } from '../../src/di/module-registry';
|
|
6
|
+
import { MODULE_METADATA_KEY } from '../../src/di/module';
|
|
7
|
+
import { ControllerRegistry } from '../../src/controller/controller';
|
|
8
|
+
|
|
9
|
+
describe('ConfigCenterModule', () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
// 清除模块元数据
|
|
12
|
+
Reflect.deleteMetadata(MODULE_METADATA_KEY, ConfigCenterModule);
|
|
13
|
+
ControllerRegistry.getInstance().clear();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('should register config center provider', () => {
|
|
17
|
+
ConfigCenterModule.forRoot({
|
|
18
|
+
provider: 'nacos',
|
|
19
|
+
nacos: {
|
|
20
|
+
client: {
|
|
21
|
+
serverList: ['http://localhost:8848'],
|
|
22
|
+
namespaceId: 'public',
|
|
23
|
+
},
|
|
24
|
+
watchInterval: 3000,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, ConfigCenterModule);
|
|
29
|
+
expect(metadata).toBeDefined();
|
|
30
|
+
expect(metadata.providers).toBeDefined();
|
|
31
|
+
|
|
32
|
+
const configCenterProvider = metadata.providers.find(
|
|
33
|
+
(provider: any) => provider.provide === CONFIG_CENTER_TOKEN,
|
|
34
|
+
);
|
|
35
|
+
expect(configCenterProvider).toBeDefined();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('should throw error when provider is not supported', () => {
|
|
39
|
+
expect(() => {
|
|
40
|
+
ConfigCenterModule.forRoot({
|
|
41
|
+
provider: 'unsupported' as any,
|
|
42
|
+
} as any);
|
|
43
|
+
}).toThrow('Unsupported config center provider');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('should throw error when nacos config is missing', () => {
|
|
47
|
+
expect(() => {
|
|
48
|
+
ConfigCenterModule.forRoot({
|
|
49
|
+
provider: 'nacos',
|
|
50
|
+
} as any);
|
|
51
|
+
}).toThrow('Nacos configuration is required');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('ConfigCenter Interface (Mock)', () => {
|
|
56
|
+
test('should implement ConfigCenter interface', async () => {
|
|
57
|
+
const mockConfigCenter: ConfigCenter = {
|
|
58
|
+
async getConfig(dataId: string, groupName: string, namespaceId?: string) {
|
|
59
|
+
return {
|
|
60
|
+
content: JSON.stringify({ key: 'value' }),
|
|
61
|
+
md5: 'abc123',
|
|
62
|
+
lastModified: Date.now(),
|
|
63
|
+
contentType: 'application/json',
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
watchConfig(dataId: string, groupName: string, listener: any, namespaceId?: string) {
|
|
67
|
+
return () => {}; // 返回取消监听的函数
|
|
68
|
+
},
|
|
69
|
+
async close() {},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const result = await mockConfigCenter.getConfig('test-config', 'DEFAULT_GROUP');
|
|
73
|
+
expect(result.content).toBeDefined();
|
|
74
|
+
expect(result.md5).toBeDefined();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach } from 'bun:test';
|
|
2
|
+
import { CircuitBreaker, CircuitBreakerState } from '../../src/microservice/governance/circuit-breaker';
|
|
3
|
+
import { RateLimiter } from '../../src/microservice/governance/rate-limiter';
|
|
4
|
+
import { RetryStrategyImpl } from '../../src/microservice/governance';
|
|
5
|
+
|
|
6
|
+
describe('CircuitBreaker', () => {
|
|
7
|
+
let circuitBreaker: CircuitBreaker;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
circuitBreaker = new CircuitBreaker({
|
|
11
|
+
failureThreshold: 0.5,
|
|
12
|
+
timeWindow: 60000,
|
|
13
|
+
minimumRequests: 5,
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('should start in CLOSED state', () => {
|
|
18
|
+
expect(circuitBreaker.getState()).toBe(CircuitBreakerState.CLOSED);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('should execute successful request', async () => {
|
|
22
|
+
const result = await circuitBreaker.execute(async () => {
|
|
23
|
+
return { success: true };
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
expect(result).toEqual({ success: true });
|
|
27
|
+
expect(circuitBreaker.getState()).toBe(CircuitBreakerState.CLOSED);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('should use fallback when circuit is open', async () => {
|
|
31
|
+
// 模拟多次失败以打开熔断器
|
|
32
|
+
for (let i = 0; i < 10; i++) {
|
|
33
|
+
try {
|
|
34
|
+
await circuitBreaker.execute(async () => {
|
|
35
|
+
throw new Error('Service error');
|
|
36
|
+
});
|
|
37
|
+
} catch {
|
|
38
|
+
// 忽略错误
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 熔断器应该打开
|
|
43
|
+
const fallback = async () => ({ fallback: true });
|
|
44
|
+
const result = await circuitBreaker.execute(async () => {
|
|
45
|
+
throw new Error('Service error');
|
|
46
|
+
}, fallback);
|
|
47
|
+
|
|
48
|
+
expect(result).toEqual({ fallback: true });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('RateLimiter', () => {
|
|
53
|
+
let rateLimiter: RateLimiter;
|
|
54
|
+
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
rateLimiter = new RateLimiter({
|
|
57
|
+
requestsPerSecond: 10,
|
|
58
|
+
timeWindow: 1000,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('should allow requests within limit', async () => {
|
|
63
|
+
for (let i = 0; i < 10; i++) {
|
|
64
|
+
const allowed = await rateLimiter.allow('test-key');
|
|
65
|
+
expect(allowed).toBe(true);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('should reject requests exceeding limit', async () => {
|
|
70
|
+
// 先允许 10 个请求
|
|
71
|
+
for (let i = 0; i < 10; i++) {
|
|
72
|
+
await rateLimiter.allow('test-key');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 第 11 个请求应该被拒绝
|
|
76
|
+
const allowed = await rateLimiter.allow('test-key');
|
|
77
|
+
expect(allowed).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('should get remaining requests', () => {
|
|
81
|
+
rateLimiter.allow('test-key');
|
|
82
|
+
const remaining = rateLimiter.getRemaining('test-key');
|
|
83
|
+
expect(remaining).toBeLessThan(10);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('should reset rate limiter', async () => {
|
|
87
|
+
// 使用完所有请求
|
|
88
|
+
for (let i = 0; i < 10; i++) {
|
|
89
|
+
await rateLimiter.allow('test-key');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
rateLimiter.reset('test-key');
|
|
93
|
+
const allowed = await rateLimiter.allow('test-key');
|
|
94
|
+
expect(allowed).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('RetryStrategy', () => {
|
|
99
|
+
test('RetryStrategyImpl should retry with fixed delay', async () => {
|
|
100
|
+
const strategy = new RetryStrategyImpl({
|
|
101
|
+
maxRetries: 3,
|
|
102
|
+
retryDelay: 10, // 使用较小的延迟以便测试快速完成
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
let attempts = 0;
|
|
106
|
+
const result = await strategy.execute(async () => {
|
|
107
|
+
attempts++;
|
|
108
|
+
if (attempts < 3) {
|
|
109
|
+
throw new Error('Retry needed');
|
|
110
|
+
}
|
|
111
|
+
return { success: true };
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(result).toEqual({ success: true });
|
|
115
|
+
expect(attempts).toBe(3);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('RetryStrategyImpl should retry with exponential delay', async () => {
|
|
119
|
+
const strategy = new RetryStrategyImpl({
|
|
120
|
+
maxRetries: 3,
|
|
121
|
+
retryDelay: 10,
|
|
122
|
+
exponentialBackoff: true,
|
|
123
|
+
baseDelay: 10,
|
|
124
|
+
maxDelay: 1000,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
let attempts = 0;
|
|
128
|
+
const result = await strategy.execute(async () => {
|
|
129
|
+
attempts++;
|
|
130
|
+
if (attempts < 3) {
|
|
131
|
+
throw new Error('Retry needed');
|
|
132
|
+
}
|
|
133
|
+
return { success: true };
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(result).toEqual({ success: true });
|
|
137
|
+
expect(attempts).toBe(3);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('should respect maxRetries limit', async () => {
|
|
141
|
+
const strategy = new RetryStrategyImpl({
|
|
142
|
+
maxRetries: 2,
|
|
143
|
+
retryDelay: 10,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
let attempts = 0;
|
|
147
|
+
await expect(
|
|
148
|
+
strategy.execute(async () => {
|
|
149
|
+
attempts++;
|
|
150
|
+
throw new Error('Always fail');
|
|
151
|
+
}),
|
|
152
|
+
).rejects.toThrow('Always fail');
|
|
153
|
+
|
|
154
|
+
expect(attempts).toBe(3); // 1 initial + 2 retries
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach } from 'bun:test';
|
|
2
|
+
import { ServiceMetricsCollector } from '../../src/microservice/monitoring/metrics-collector';
|
|
3
|
+
|
|
4
|
+
describe('ServiceMetricsCollector', () => {
|
|
5
|
+
let collector: ServiceMetricsCollector;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
collector = new ServiceMetricsCollector({
|
|
9
|
+
enabled: true,
|
|
10
|
+
autoReportToMetrics: false, // 测试时不自动上报
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('should create metrics collector', () => {
|
|
15
|
+
expect(collector).toBeDefined();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('should record service call', () => {
|
|
19
|
+
collector.recordCall('test-service', '127.0.0.1:3000', true, 100);
|
|
20
|
+
|
|
21
|
+
const metrics = collector.getMetrics('test-service');
|
|
22
|
+
expect(metrics.length).toBeGreaterThan(0);
|
|
23
|
+
expect(metrics[0]?.totalRequests).toBe(1);
|
|
24
|
+
expect(metrics[0]?.successRequests).toBe(1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('should calculate error rate', () => {
|
|
28
|
+
// 记录 10 次调用,5 次成功,5 次失败
|
|
29
|
+
for (let i = 0; i < 5; i++) {
|
|
30
|
+
collector.recordCall('test-service', '127.0.0.1:3000', true, 100);
|
|
31
|
+
}
|
|
32
|
+
for (let i = 0; i < 5; i++) {
|
|
33
|
+
collector.recordCall('test-service', '127.0.0.1:3000', false, 200);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const metrics = collector.getMetrics('test-service');
|
|
37
|
+
expect(metrics[0]?.errorRate).toBe(0.5);
|
|
38
|
+
expect(metrics[0]?.totalRequests).toBe(10);
|
|
39
|
+
expect(metrics[0]?.successRequests).toBe(5);
|
|
40
|
+
expect(metrics[0]?.failureRequests).toBe(5);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('should track health status', () => {
|
|
44
|
+
// 记录多次失败
|
|
45
|
+
for (let i = 0; i < 3; i++) {
|
|
46
|
+
collector.recordCall('test-service', '127.0.0.1:3000', false, 500);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const healthStatus = collector.getHealthStatus('test-service');
|
|
50
|
+
expect(healthStatus.length).toBeGreaterThan(0);
|
|
51
|
+
expect(healthStatus[0]?.healthy).toBe(false);
|
|
52
|
+
expect(healthStatus[0]?.consecutiveFailures).toBeGreaterThanOrEqual(3);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('should reset metrics', () => {
|
|
56
|
+
collector.recordCall('test-service', '127.0.0.1:3000', true, 100);
|
|
57
|
+
collector.reset('test-service', '127.0.0.1:3000');
|
|
58
|
+
|
|
59
|
+
const metrics = collector.getMetrics('test-service');
|
|
60
|
+
expect(metrics.length).toBe(0);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('should calculate latency statistics', () => {
|
|
64
|
+
const latencies = [50, 100, 150, 200, 250];
|
|
65
|
+
for (const latency of latencies) {
|
|
66
|
+
collector.recordCall('test-service', '127.0.0.1:3000', true, latency);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const metrics = collector.getMetrics('test-service');
|
|
70
|
+
expect(metrics[0]?.averageLatency).toBe(150);
|
|
71
|
+
expect(metrics[0]?.minLatency).toBe(50);
|
|
72
|
+
expect(metrics[0]?.maxLatency).toBe(250);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|