@dangao/bun-server 1.4.0 → 1.6.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.
@@ -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
+ });