@dangao/bun-server 1.7.0 → 1.7.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/README.md +72 -3
- package/dist/cache/cache-module.d.ts +18 -0
- package/dist/cache/cache-module.d.ts.map +1 -1
- package/dist/cache/index.d.ts +3 -1
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/interceptors.d.ts +41 -0
- package/dist/cache/interceptors.d.ts.map +1 -0
- package/dist/cache/service-proxy.d.ts +62 -0
- package/dist/cache/service-proxy.d.ts.map +1 -0
- package/dist/controller/controller.d.ts +8 -0
- package/dist/controller/controller.d.ts.map +1 -1
- package/dist/core/application.d.ts +5 -0
- package/dist/core/application.d.ts.map +1 -1
- package/dist/di/container.d.ts +18 -1
- package/dist/di/container.d.ts.map +1 -1
- package/dist/di/index.d.ts +1 -1
- package/dist/di/index.d.ts.map +1 -1
- package/dist/di/types.d.ts +22 -0
- package/dist/di/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3163 -2862
- package/docs/symbol-interface-pattern.md +431 -0
- package/docs/zh/symbol-interface-pattern.md +431 -0
- package/package.json +1 -1
- package/src/cache/cache-module.ts +37 -0
- package/src/cache/index.ts +16 -1
- package/src/cache/interceptors.ts +295 -0
- package/src/cache/service-proxy.ts +219 -0
- package/src/controller/controller.ts +30 -6
- package/src/core/application.ts +25 -1
- package/src/di/container.ts +57 -7
- package/src/di/index.ts +7 -1
- package/src/di/types.ts +29 -0
- package/src/index.ts +7 -0
- package/tests/cache/cache-decorators.test.ts +284 -0
- package/tests/controller/path-combination.test.ts +353 -0
package/src/di/container.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
import {
|
|
3
3
|
type DependencyMetadata,
|
|
4
|
+
type InstancePostProcessor,
|
|
5
|
+
INSTANCE_POST_PROCESSOR_TOKEN,
|
|
4
6
|
Lifecycle,
|
|
5
7
|
type ProviderConfig,
|
|
6
8
|
} from "./types";
|
|
@@ -59,6 +61,11 @@ export class Container {
|
|
|
59
61
|
DependencyPlan
|
|
60
62
|
>();
|
|
61
63
|
|
|
64
|
+
/**
|
|
65
|
+
* 实例后处理器列表
|
|
66
|
+
*/
|
|
67
|
+
private readonly postProcessors: InstancePostProcessor[] = [];
|
|
68
|
+
|
|
62
69
|
/**
|
|
63
70
|
* 注册提供者
|
|
64
71
|
* @param token - 提供者标识符(类构造函数或 token)
|
|
@@ -112,6 +119,45 @@ export class Container {
|
|
|
112
119
|
});
|
|
113
120
|
}
|
|
114
121
|
|
|
122
|
+
/**
|
|
123
|
+
* 注册实例后处理器
|
|
124
|
+
* @param processor - 后处理器实例
|
|
125
|
+
*/
|
|
126
|
+
public registerPostProcessor(processor: InstancePostProcessor): void {
|
|
127
|
+
this.postProcessors.push(processor);
|
|
128
|
+
// 按优先级排序(数字越小优先级越高)
|
|
129
|
+
this.postProcessors.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 应用所有后处理器(包括父容器的后处理器)
|
|
134
|
+
* @param instance - 原始实例
|
|
135
|
+
* @param constructor - 构造函数
|
|
136
|
+
* @param originContainer - 原始调用容器(用于后处理器解析依赖)
|
|
137
|
+
* @returns 处理后的实例
|
|
138
|
+
*/
|
|
139
|
+
private applyPostProcessors<T>(
|
|
140
|
+
instance: T,
|
|
141
|
+
constructor: Constructor<T>,
|
|
142
|
+
originContainer?: Container,
|
|
143
|
+
): T {
|
|
144
|
+
// 使用原始容器或当前容器
|
|
145
|
+
const containerForProcessors = originContainer ?? this;
|
|
146
|
+
let result = instance;
|
|
147
|
+
|
|
148
|
+
// 先应用父容器的后处理器
|
|
149
|
+
if (this.parent) {
|
|
150
|
+
result = (this.parent as Container).applyPostProcessors(result, constructor, containerForProcessors);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 再应用本容器的后处理器
|
|
154
|
+
// 传递原始调用容器,而不是当前容器
|
|
155
|
+
for (const processor of this.postProcessors) {
|
|
156
|
+
result = processor.postProcess(result, constructor, containerForProcessors);
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
|
|
115
161
|
/**
|
|
116
162
|
* 解析依赖
|
|
117
163
|
* @param token - 提供者标识符
|
|
@@ -321,16 +367,19 @@ export class Container {
|
|
|
321
367
|
private instantiate<T>(constructor: Constructor<T>): T {
|
|
322
368
|
const plan = this.getDependencyPlan(constructor);
|
|
323
369
|
|
|
370
|
+
let instance: T;
|
|
324
371
|
if (plan.paramLength === 0) {
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
372
|
+
instance = new constructor();
|
|
373
|
+
} else {
|
|
374
|
+
const dependencies = new Array(plan.paramLength);
|
|
375
|
+
for (let index = 0; index < plan.paramLength; index++) {
|
|
376
|
+
dependencies[index] = this.resolveFromPlan(constructor, plan, index);
|
|
377
|
+
}
|
|
378
|
+
instance = new constructor(...dependencies);
|
|
331
379
|
}
|
|
332
380
|
|
|
333
|
-
|
|
381
|
+
// 应用后处理器
|
|
382
|
+
return this.applyPostProcessors(instance, constructor);
|
|
334
383
|
}
|
|
335
384
|
|
|
336
385
|
/**
|
|
@@ -356,6 +405,7 @@ export class Container {
|
|
|
356
405
|
this.singletons.clear();
|
|
357
406
|
this.typeToToken.clear();
|
|
358
407
|
this.dependencyPlans.clear();
|
|
408
|
+
this.postProcessors.length = 0;
|
|
359
409
|
// scopedInstances 使用 WeakMap,当 Context 对象被 GC 时会自动清理
|
|
360
410
|
}
|
|
361
411
|
|
package/src/di/index.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export { Container } from './container';
|
|
2
2
|
export { Injectable, Inject, getDependencyMetadata, isInjectable, getLifecycle } from './decorators';
|
|
3
|
-
export {
|
|
3
|
+
export {
|
|
4
|
+
Lifecycle,
|
|
5
|
+
INSTANCE_POST_PROCESSOR_TOKEN,
|
|
6
|
+
type ProviderConfig,
|
|
7
|
+
type DependencyMetadata,
|
|
8
|
+
type InstancePostProcessor,
|
|
9
|
+
} from './types';
|
|
4
10
|
|
package/src/di/types.ts
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
import type { Constructor } from '@/core/types'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* 实例后处理器接口
|
|
5
|
+
* 用于在 DI 容器创建实例后进行处理(如创建代理、注入额外依赖等)
|
|
6
|
+
*/
|
|
7
|
+
export interface InstancePostProcessor {
|
|
8
|
+
/**
|
|
9
|
+
* 处理新创建的实例
|
|
10
|
+
* @param instance - 原始实例
|
|
11
|
+
* @param constructor - 构造函数
|
|
12
|
+
* @param container - DI 容器引用
|
|
13
|
+
* @returns 处理后的实例(可以是代理)
|
|
14
|
+
*/
|
|
15
|
+
postProcess<T>(
|
|
16
|
+
instance: T,
|
|
17
|
+
constructor: Constructor<T>,
|
|
18
|
+
container: unknown,
|
|
19
|
+
): T;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 优先级(数字越小优先级越高,默认 100)
|
|
23
|
+
*/
|
|
24
|
+
priority?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 实例后处理器注册表 Token
|
|
29
|
+
*/
|
|
30
|
+
export const INSTANCE_POST_PROCESSOR_TOKEN = Symbol('@dangao/bun-server:di:post-processor');
|
|
31
|
+
|
|
3
32
|
/**
|
|
4
33
|
* 依赖生命周期类型
|
|
5
34
|
*/
|
package/src/index.ts
CHANGED
|
@@ -254,10 +254,17 @@ export {
|
|
|
254
254
|
Cacheable,
|
|
255
255
|
CacheEvict,
|
|
256
256
|
CachePut,
|
|
257
|
+
EnableCacheProxy,
|
|
258
|
+
CacheServiceProxy,
|
|
259
|
+
CachePostProcessor,
|
|
260
|
+
CacheableInterceptor,
|
|
261
|
+
CacheEvictInterceptor,
|
|
262
|
+
CachePutInterceptor,
|
|
257
263
|
MemoryCacheStore,
|
|
258
264
|
RedisCacheStore,
|
|
259
265
|
CACHE_SERVICE_TOKEN,
|
|
260
266
|
CACHE_OPTIONS_TOKEN,
|
|
267
|
+
CACHE_POST_PROCESSOR_TOKEN,
|
|
261
268
|
} from './cache';
|
|
262
269
|
export type {
|
|
263
270
|
CacheModuleOptions,
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
|
|
3
|
+
import { Application } from '../../src/core/application';
|
|
4
|
+
import { Controller } from '../../src/controller/controller';
|
|
5
|
+
import { GET } from '../../src/router/decorators';
|
|
6
|
+
import { Param } from '../../src/controller/decorators';
|
|
7
|
+
import { Module } from '../../src/di/module';
|
|
8
|
+
import { Injectable, Inject } from '../../src/di/decorators';
|
|
9
|
+
import {
|
|
10
|
+
CacheModule,
|
|
11
|
+
CacheService,
|
|
12
|
+
Cacheable,
|
|
13
|
+
CacheEvict,
|
|
14
|
+
CachePut,
|
|
15
|
+
EnableCacheProxy,
|
|
16
|
+
CACHE_SERVICE_TOKEN,
|
|
17
|
+
} from '../../src/cache';
|
|
18
|
+
import { RouteRegistry } from '../../src/router/registry';
|
|
19
|
+
import { ControllerRegistry } from '../../src/controller/controller';
|
|
20
|
+
import { ModuleRegistry } from '../../src/di/module-registry';
|
|
21
|
+
import { getTestPort } from '../utils/test-port';
|
|
22
|
+
|
|
23
|
+
describe('Cache Decorators', () => {
|
|
24
|
+
let app: Application;
|
|
25
|
+
let port: number;
|
|
26
|
+
let callCount: number;
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
port = getTestPort();
|
|
30
|
+
callCount = 0;
|
|
31
|
+
CacheModule.reset();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(async () => {
|
|
35
|
+
if (app) {
|
|
36
|
+
await app.stop();
|
|
37
|
+
}
|
|
38
|
+
RouteRegistry.getInstance().clear();
|
|
39
|
+
ControllerRegistry.getInstance().clear();
|
|
40
|
+
ModuleRegistry.getInstance().clear();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('@Cacheable should cache method results in service layer', async () => {
|
|
44
|
+
// 定义服务
|
|
45
|
+
@Injectable()
|
|
46
|
+
@EnableCacheProxy()
|
|
47
|
+
class UserService {
|
|
48
|
+
@Cacheable({ key: 'user:{0}', ttl: 5000 })
|
|
49
|
+
public async findById(id: string): Promise<{ id: string; name: string }> {
|
|
50
|
+
callCount++;
|
|
51
|
+
return { id, name: `User ${id}` };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 定义控制器
|
|
56
|
+
@Controller('/api/users')
|
|
57
|
+
class UserController {
|
|
58
|
+
public constructor(private readonly userService: UserService) {}
|
|
59
|
+
|
|
60
|
+
@GET('/:id')
|
|
61
|
+
public async getUser(@Param('id') id: string): Promise<{ id: string; name: string }> {
|
|
62
|
+
return await this.userService.findById(id);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 定义模块
|
|
67
|
+
@Module({
|
|
68
|
+
imports: [CacheModule.forRoot({ defaultTtl: 5000 })],
|
|
69
|
+
controllers: [UserController],
|
|
70
|
+
providers: [UserService],
|
|
71
|
+
})
|
|
72
|
+
class AppModule {}
|
|
73
|
+
|
|
74
|
+
// 创建应用
|
|
75
|
+
app = new Application({ port });
|
|
76
|
+
app.registerModule(AppModule);
|
|
77
|
+
await app.listen();
|
|
78
|
+
|
|
79
|
+
// 第一次调用 - 应该执行方法
|
|
80
|
+
const response1 = await fetch(`http://localhost:${port}/api/users/1`);
|
|
81
|
+
expect(response1.status).toBe(200);
|
|
82
|
+
const data1 = await response1.json();
|
|
83
|
+
expect(data1.id).toBe('1');
|
|
84
|
+
expect(data1.name).toBe('User 1');
|
|
85
|
+
expect(callCount).toBe(1);
|
|
86
|
+
|
|
87
|
+
// 第二次调用 - 应该使用缓存
|
|
88
|
+
const response2 = await fetch(`http://localhost:${port}/api/users/1`);
|
|
89
|
+
expect(response2.status).toBe(200);
|
|
90
|
+
const data2 = await response2.json();
|
|
91
|
+
expect(data2.id).toBe('1');
|
|
92
|
+
expect(data2.name).toBe('User 1');
|
|
93
|
+
expect(callCount).toBe(1); // 方法没有被再次调用
|
|
94
|
+
|
|
95
|
+
// 调用不同的 ID - 应该执行方法
|
|
96
|
+
const response3 = await fetch(`http://localhost:${port}/api/users/2`);
|
|
97
|
+
expect(response3.status).toBe(200);
|
|
98
|
+
const data3 = await response3.json();
|
|
99
|
+
expect(data3.id).toBe('2');
|
|
100
|
+
expect(data3.name).toBe('User 2');
|
|
101
|
+
expect(callCount).toBe(2);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('@CacheEvict should clear cache', async () => {
|
|
105
|
+
// 定义服务
|
|
106
|
+
@Injectable()
|
|
107
|
+
@EnableCacheProxy()
|
|
108
|
+
class ProductService {
|
|
109
|
+
@Cacheable({ key: 'product:{0}', ttl: 5000 })
|
|
110
|
+
public async findById(id: string): Promise<{ id: string; name: string }> {
|
|
111
|
+
callCount++;
|
|
112
|
+
return { id, name: `Product ${id} v${callCount}` };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@CacheEvict({ key: 'product:{0}' })
|
|
116
|
+
public async deleteById(id: string): Promise<void> {
|
|
117
|
+
// 删除产品
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 定义控制器
|
|
122
|
+
@Controller('/api/products')
|
|
123
|
+
class ProductController {
|
|
124
|
+
public constructor(private readonly productService: ProductService) {}
|
|
125
|
+
|
|
126
|
+
@GET('/:id')
|
|
127
|
+
public async getProduct(@Param('id') id: string): Promise<{ id: string; name: string }> {
|
|
128
|
+
return await this.productService.findById(id);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@GET('/:id/delete')
|
|
132
|
+
public async deleteProduct(@Param('id') id: string): Promise<{ success: boolean }> {
|
|
133
|
+
await this.productService.deleteById(id);
|
|
134
|
+
return { success: true };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 定义模块
|
|
139
|
+
@Module({
|
|
140
|
+
imports: [CacheModule.forRoot({ defaultTtl: 5000 })],
|
|
141
|
+
controllers: [ProductController],
|
|
142
|
+
providers: [ProductService],
|
|
143
|
+
})
|
|
144
|
+
class AppModule {}
|
|
145
|
+
|
|
146
|
+
// 创建应用
|
|
147
|
+
app = new Application({ port });
|
|
148
|
+
app.registerModule(AppModule);
|
|
149
|
+
await app.listen();
|
|
150
|
+
|
|
151
|
+
// 第一次调用 - 缓存产品
|
|
152
|
+
const response1 = await fetch(`http://localhost:${port}/api/products/1`);
|
|
153
|
+
expect(response1.status).toBe(200);
|
|
154
|
+
const data1 = await response1.json();
|
|
155
|
+
expect(data1.name).toBe('Product 1 v1');
|
|
156
|
+
expect(callCount).toBe(1);
|
|
157
|
+
|
|
158
|
+
// 第二次调用 - 使用缓存
|
|
159
|
+
const response2 = await fetch(`http://localhost:${port}/api/products/1`);
|
|
160
|
+
const data2 = await response2.json();
|
|
161
|
+
expect(data2.name).toBe('Product 1 v1');
|
|
162
|
+
expect(callCount).toBe(1);
|
|
163
|
+
|
|
164
|
+
// 删除产品 - 清除缓存
|
|
165
|
+
await fetch(`http://localhost:${port}/api/products/1/delete`);
|
|
166
|
+
|
|
167
|
+
// 再次获取 - 缓存已清除,应重新执行方法
|
|
168
|
+
const response3 = await fetch(`http://localhost:${port}/api/products/1`);
|
|
169
|
+
const data3 = await response3.json();
|
|
170
|
+
expect(data3.name).toBe('Product 1 v2');
|
|
171
|
+
expect(callCount).toBe(2);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('@CachePut should always execute method and update cache', async () => {
|
|
175
|
+
// 定义服务
|
|
176
|
+
@Injectable()
|
|
177
|
+
@EnableCacheProxy()
|
|
178
|
+
class OrderService {
|
|
179
|
+
@Cacheable({ key: 'order:{0}', ttl: 5000 })
|
|
180
|
+
public async findById(id: string): Promise<{ id: string; status: string }> {
|
|
181
|
+
callCount++;
|
|
182
|
+
return { id, status: 'pending' };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@CachePut({ key: 'order:{0}', ttl: 5000 })
|
|
186
|
+
public async updateStatus(id: string, status: string): Promise<{ id: string; status: string }> {
|
|
187
|
+
return { id, status };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 定义控制器
|
|
192
|
+
@Controller('/api/orders')
|
|
193
|
+
class OrderController {
|
|
194
|
+
public constructor(private readonly orderService: OrderService) {}
|
|
195
|
+
|
|
196
|
+
@GET('/:id')
|
|
197
|
+
public async getOrder(@Param('id') id: string): Promise<{ id: string; status: string }> {
|
|
198
|
+
return await this.orderService.findById(id);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
@GET('/:id/update/:status')
|
|
202
|
+
public async updateOrder(
|
|
203
|
+
@Param('id') id: string,
|
|
204
|
+
@Param('status') status: string,
|
|
205
|
+
): Promise<{ id: string; status: string }> {
|
|
206
|
+
return await this.orderService.updateStatus(id, status);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 定义模块
|
|
211
|
+
@Module({
|
|
212
|
+
imports: [CacheModule.forRoot({ defaultTtl: 5000 })],
|
|
213
|
+
controllers: [OrderController],
|
|
214
|
+
providers: [OrderService],
|
|
215
|
+
})
|
|
216
|
+
class AppModule {}
|
|
217
|
+
|
|
218
|
+
// 创建应用
|
|
219
|
+
app = new Application({ port });
|
|
220
|
+
app.registerModule(AppModule);
|
|
221
|
+
await app.listen();
|
|
222
|
+
|
|
223
|
+
// 第一次调用 - 缓存订单
|
|
224
|
+
const response1 = await fetch(`http://localhost:${port}/api/orders/1`);
|
|
225
|
+
expect(response1.status).toBe(200);
|
|
226
|
+
const data1 = await response1.json();
|
|
227
|
+
expect(data1.status).toBe('pending');
|
|
228
|
+
expect(callCount).toBe(1);
|
|
229
|
+
|
|
230
|
+
// 更新订单状态 - @CachePut 会更新缓存
|
|
231
|
+
const response2 = await fetch(`http://localhost:${port}/api/orders/1/update/completed`);
|
|
232
|
+
const data2 = await response2.json();
|
|
233
|
+
expect(data2.status).toBe('completed');
|
|
234
|
+
|
|
235
|
+
// 再次获取 - 应该返回更新后的缓存
|
|
236
|
+
const response3 = await fetch(`http://localhost:${port}/api/orders/1`);
|
|
237
|
+
const data3 = await response3.json();
|
|
238
|
+
expect(data3.status).toBe('completed');
|
|
239
|
+
expect(callCount).toBe(1); // findById 没有被再次调用
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('Service without @EnableCacheProxy should not use cache', async () => {
|
|
243
|
+
// 定义服务(没有 @EnableCacheProxy)
|
|
244
|
+
@Injectable()
|
|
245
|
+
class NoCacheService {
|
|
246
|
+
@Cacheable({ key: 'nocache:{0}', ttl: 5000 })
|
|
247
|
+
public async findById(id: string): Promise<{ id: string }> {
|
|
248
|
+
callCount++;
|
|
249
|
+
return { id };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 定义控制器
|
|
254
|
+
@Controller('/api/nocache')
|
|
255
|
+
class NoCacheController {
|
|
256
|
+
public constructor(private readonly noCacheService: NoCacheService) {}
|
|
257
|
+
|
|
258
|
+
@GET('/:id')
|
|
259
|
+
public async get(@Param('id') id: string): Promise<{ id: string }> {
|
|
260
|
+
return await this.noCacheService.findById(id);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// 定义模块
|
|
265
|
+
@Module({
|
|
266
|
+
imports: [CacheModule.forRoot({ defaultTtl: 5000 })],
|
|
267
|
+
controllers: [NoCacheController],
|
|
268
|
+
providers: [NoCacheService],
|
|
269
|
+
})
|
|
270
|
+
class AppModule {}
|
|
271
|
+
|
|
272
|
+
// 创建应用
|
|
273
|
+
app = new Application({ port });
|
|
274
|
+
app.registerModule(AppModule);
|
|
275
|
+
await app.listen();
|
|
276
|
+
|
|
277
|
+
// 两次调用都应该执行方法(没有缓存)
|
|
278
|
+
await fetch(`http://localhost:${port}/api/nocache/1`);
|
|
279
|
+
expect(callCount).toBe(1);
|
|
280
|
+
|
|
281
|
+
await fetch(`http://localhost:${port}/api/nocache/1`);
|
|
282
|
+
expect(callCount).toBe(2); // 方法被再次调用
|
|
283
|
+
});
|
|
284
|
+
});
|