imean-service-engine 2.0.0 → 2.0.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/index.d.mts +77 -52
- package/dist/index.d.ts +77 -52
- package/dist/index.js +2078 -1945
- package/dist/index.mjs +2076 -1944
- package/package.json +9 -2
- package/.vscode/settings.json +0 -8
- package/src/core/checker.ts +0 -33
- package/src/core/decorators.test.ts +0 -96
- package/src/core/decorators.ts +0 -68
- package/src/core/engine.test.ts +0 -218
- package/src/core/engine.ts +0 -635
- package/src/core/errors.ts +0 -28
- package/src/core/factory.test.ts +0 -73
- package/src/core/factory.ts +0 -92
- package/src/core/logger.ts +0 -65
- package/src/core/testing.ts +0 -73
- package/src/core/types.ts +0 -191
- package/src/index.ts +0 -49
- package/src/metadata/README.md +0 -422
- package/src/metadata/metadata.test.ts +0 -369
- package/src/metadata/metadata.ts +0 -512
- package/src/plugins/action/action-plugin.test.ts +0 -660
- package/src/plugins/action/decorator.ts +0 -14
- package/src/plugins/action/index.ts +0 -4
- package/src/plugins/action/plugin.ts +0 -349
- package/src/plugins/action/types.ts +0 -49
- package/src/plugins/action/utils.test.ts +0 -196
- package/src/plugins/action/utils.ts +0 -111
- package/src/plugins/cache/adapter.test.ts +0 -689
- package/src/plugins/cache/adapter.ts +0 -324
- package/src/plugins/cache/cache-plugin.test.ts +0 -269
- package/src/plugins/cache/decorator.ts +0 -26
- package/src/plugins/cache/index.ts +0 -20
- package/src/plugins/cache/plugin.ts +0 -299
- package/src/plugins/cache/types.ts +0 -69
- package/src/plugins/client-code/client-code-plugin.test.ts +0 -511
- package/src/plugins/client-code/format.ts +0 -9
- package/src/plugins/client-code/generator.test.ts +0 -52
- package/src/plugins/client-code/generator.ts +0 -263
- package/src/plugins/client-code/index.ts +0 -15
- package/src/plugins/client-code/plugin.ts +0 -158
- package/src/plugins/client-code/types.ts +0 -52
- package/src/plugins/client-code/utils.ts +0 -164
- package/src/plugins/graceful-shutdown/graceful-shutdown-plugin.test.ts +0 -401
- package/src/plugins/graceful-shutdown/index.ts +0 -3
- package/src/plugins/graceful-shutdown/plugin.ts +0 -279
- package/src/plugins/graceful-shutdown/types.ts +0 -17
- package/src/plugins/rate-limit/rate-limit-plugin.example.ts +0 -171
- package/src/plugins/route/components/Layout.tsx +0 -42
- package/src/plugins/route/components/ServiceStatusPage.tsx +0 -141
- package/src/plugins/route/decorator.ts +0 -50
- package/src/plugins/route/index.ts +0 -16
- package/src/plugins/route/plugin.ts +0 -218
- package/src/plugins/route/route-plugin.test.ts +0 -759
- package/src/plugins/route/types.ts +0 -72
- package/src/plugins/schedule/README.md +0 -309
- package/src/plugins/schedule/decorator.ts +0 -25
- package/src/plugins/schedule/index.ts +0 -12
- package/src/plugins/schedule/mock-etcd.ts +0 -145
- package/src/plugins/schedule/plugin.ts +0 -164
- package/src/plugins/schedule/schedule-plugin.test.ts +0 -312
- package/src/plugins/schedule/scheduler.ts +0 -164
- package/src/plugins/schedule/types.ts +0 -94
- package/src/plugins/schedule/utils.test.ts +0 -163
- package/src/plugins/schedule/utils.ts +0 -41
- package/tests/integration/client.test.ts +0 -203
- package/tests/integration/dev-service.ts +0 -301
- package/tests/integration/generated/client.ts +0 -123
- package/tests/integration/start-service.ts +0 -21
- package/tsconfig.json +0 -27
- package/tsup.config.ts +0 -16
- package/vitest.config.ts +0 -19
|
@@ -1,689 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { MemoryCacheAdapter, RedisCacheAdapter } from "./adapter";
|
|
3
|
-
import { CacheItem } from "./types";
|
|
4
|
-
|
|
5
|
-
describe("CacheAdapter", () => {
|
|
6
|
-
describe("MemoryCacheAdapter", () => {
|
|
7
|
-
let adapter: MemoryCacheAdapter;
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
adapter = new MemoryCacheAdapter();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
describe("基本操作", () => {
|
|
14
|
-
it("应该能够设置和获取缓存项", async () => {
|
|
15
|
-
const item: CacheItem<string> = {
|
|
16
|
-
value: "test-value",
|
|
17
|
-
expiresAt: Date.now() + 10000,
|
|
18
|
-
createdAt: Date.now(),
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
await adapter.set("test-key", item);
|
|
22
|
-
const result = await adapter.get("test-key");
|
|
23
|
-
|
|
24
|
-
expect(result).not.toBeNull();
|
|
25
|
-
expect(result?.value).toBe("test-value");
|
|
26
|
-
expect(result?.expiresAt).toBe(item.expiresAt);
|
|
27
|
-
expect(result?.createdAt).toBe(item.createdAt);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("应该返回 null 当键不存在时", async () => {
|
|
31
|
-
const result = await adapter.get("non-existent-key");
|
|
32
|
-
expect(result).toBeNull();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("应该能够删除缓存项", async () => {
|
|
36
|
-
const item: CacheItem<string> = {
|
|
37
|
-
value: "test-value",
|
|
38
|
-
expiresAt: Date.now() + 10000,
|
|
39
|
-
createdAt: Date.now(),
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
await adapter.set("test-key", item);
|
|
43
|
-
const deleted = await adapter.delete("test-key");
|
|
44
|
-
expect(deleted).toBe(true);
|
|
45
|
-
|
|
46
|
-
const result = await adapter.get("test-key");
|
|
47
|
-
expect(result).toBeNull();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("删除不存在的键应该返回 false", async () => {
|
|
51
|
-
const deleted = await adapter.delete("non-existent-key");
|
|
52
|
-
expect(deleted).toBe(false);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("应该能够清空所有缓存", async () => {
|
|
56
|
-
await adapter.set("key1", {
|
|
57
|
-
value: "value1",
|
|
58
|
-
expiresAt: Date.now() + 10000,
|
|
59
|
-
createdAt: Date.now(),
|
|
60
|
-
});
|
|
61
|
-
await adapter.set("key2", {
|
|
62
|
-
value: "value2",
|
|
63
|
-
expiresAt: Date.now() + 10000,
|
|
64
|
-
createdAt: Date.now(),
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
await adapter.clear();
|
|
68
|
-
|
|
69
|
-
const stats = await adapter.getStats();
|
|
70
|
-
expect(stats.size).toBe(0);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe("过期处理", () => {
|
|
75
|
-
it("应该自动删除过期的缓存项", async () => {
|
|
76
|
-
const expiredItem: CacheItem<string> = {
|
|
77
|
-
value: "expired-value",
|
|
78
|
-
expiresAt: Date.now() - 1000, // 已过期
|
|
79
|
-
createdAt: Date.now() - 5000,
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
await adapter.set("expired-key", expiredItem);
|
|
83
|
-
const result = await adapter.get("expired-key");
|
|
84
|
-
|
|
85
|
-
expect(result).toBeNull();
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("应该保留未过期的缓存项", async () => {
|
|
89
|
-
const validItem: CacheItem<string> = {
|
|
90
|
-
value: "valid-value",
|
|
91
|
-
expiresAt: Date.now() + 10000, // 10秒后过期
|
|
92
|
-
createdAt: Date.now(),
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
await adapter.set("valid-key", validItem);
|
|
96
|
-
const result = await adapter.get("valid-key");
|
|
97
|
-
|
|
98
|
-
expect(result).not.toBeNull();
|
|
99
|
-
expect(result?.value).toBe("valid-value");
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
describe("键管理", () => {
|
|
104
|
-
it("应该能够获取所有缓存键", async () => {
|
|
105
|
-
await adapter.set("key1", {
|
|
106
|
-
value: "value1",
|
|
107
|
-
expiresAt: Date.now() + 10000,
|
|
108
|
-
createdAt: Date.now(),
|
|
109
|
-
});
|
|
110
|
-
await adapter.set("key2", {
|
|
111
|
-
value: "value2",
|
|
112
|
-
expiresAt: Date.now() + 10000,
|
|
113
|
-
createdAt: Date.now(),
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
const keys = await adapter.keys();
|
|
117
|
-
expect(keys).toContain("key1");
|
|
118
|
-
expect(keys).toContain("key2");
|
|
119
|
-
expect(keys.length).toBe(2);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it("清空后应该返回空数组", async () => {
|
|
123
|
-
await adapter.set("key1", {
|
|
124
|
-
value: "value1",
|
|
125
|
-
expiresAt: Date.now() + 10000,
|
|
126
|
-
createdAt: Date.now(),
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
await adapter.clear();
|
|
130
|
-
const keys = await adapter.keys();
|
|
131
|
-
expect(keys).toEqual([]);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
describe("统计信息", () => {
|
|
136
|
-
it("应该能够获取缓存统计信息", async () => {
|
|
137
|
-
const now = Date.now();
|
|
138
|
-
await adapter.set("key1", {
|
|
139
|
-
value: "value1",
|
|
140
|
-
expiresAt: now + 10000,
|
|
141
|
-
createdAt: now,
|
|
142
|
-
});
|
|
143
|
-
await adapter.set("key2", {
|
|
144
|
-
value: "value2",
|
|
145
|
-
expiresAt: now + 20000,
|
|
146
|
-
createdAt: now + 1000,
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
const stats = await adapter.getStats();
|
|
150
|
-
expect(stats.size).toBe(2);
|
|
151
|
-
expect(stats.entries).toHaveLength(2);
|
|
152
|
-
expect(stats.entries[0]).toHaveProperty("key");
|
|
153
|
-
expect(stats.entries[0]).toHaveProperty("expiresAt");
|
|
154
|
-
expect(stats.entries[0]).toHaveProperty("createdAt");
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("应该排除过期的项", async () => {
|
|
158
|
-
await adapter.set("valid-key", {
|
|
159
|
-
value: "valid",
|
|
160
|
-
expiresAt: Date.now() + 10000,
|
|
161
|
-
createdAt: Date.now(),
|
|
162
|
-
});
|
|
163
|
-
await adapter.set("expired-key", {
|
|
164
|
-
value: "expired",
|
|
165
|
-
expiresAt: Date.now() - 1000,
|
|
166
|
-
createdAt: Date.now() - 5000,
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
const stats = await adapter.getStats();
|
|
170
|
-
// 过期项在 get 时会被删除,所以统计中不包含
|
|
171
|
-
expect(stats.size).toBe(1);
|
|
172
|
-
expect(stats.entries[0].key).toBe("valid-key");
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
describe("类型支持", () => {
|
|
177
|
-
it("应该支持不同的数据类型", async () => {
|
|
178
|
-
// 字符串
|
|
179
|
-
await adapter.set("string-key", {
|
|
180
|
-
value: "string-value",
|
|
181
|
-
expiresAt: Date.now() + 10000,
|
|
182
|
-
createdAt: Date.now(),
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// 数字
|
|
186
|
-
await adapter.set("number-key", {
|
|
187
|
-
value: 123,
|
|
188
|
-
expiresAt: Date.now() + 10000,
|
|
189
|
-
createdAt: Date.now(),
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
// 对象
|
|
193
|
-
await adapter.set("object-key", {
|
|
194
|
-
value: { name: "test", age: 25 },
|
|
195
|
-
expiresAt: Date.now() + 10000,
|
|
196
|
-
createdAt: Date.now(),
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
// 数组
|
|
200
|
-
await adapter.set("array-key", {
|
|
201
|
-
value: [1, 2, 3],
|
|
202
|
-
expiresAt: Date.now() + 10000,
|
|
203
|
-
createdAt: Date.now(),
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
const stringResult = await adapter.get<string>("string-key");
|
|
207
|
-
expect(stringResult?.value).toBe("string-value");
|
|
208
|
-
|
|
209
|
-
const numberResult = await adapter.get<number>("number-key");
|
|
210
|
-
expect(numberResult?.value).toBe(123);
|
|
211
|
-
|
|
212
|
-
const objectResult = await adapter.get<{ name: string; age: number }>(
|
|
213
|
-
"object-key"
|
|
214
|
-
);
|
|
215
|
-
expect(objectResult?.value).toEqual({ name: "test", age: 25 });
|
|
216
|
-
|
|
217
|
-
const arrayResult = await adapter.get<number[]>("array-key");
|
|
218
|
-
expect(arrayResult?.value).toEqual([1, 2, 3]);
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
describe("定期清理", () => {
|
|
223
|
-
it("应该能够批量清理过期项", async () => {
|
|
224
|
-
const now = Date.now();
|
|
225
|
-
|
|
226
|
-
// 添加一些过期项
|
|
227
|
-
await adapter.set("expired1", {
|
|
228
|
-
value: "expired1",
|
|
229
|
-
expiresAt: now - 1000,
|
|
230
|
-
createdAt: now - 5000,
|
|
231
|
-
});
|
|
232
|
-
await adapter.set("expired2", {
|
|
233
|
-
value: "expired2",
|
|
234
|
-
expiresAt: now - 500,
|
|
235
|
-
createdAt: now - 3000,
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// 添加一些有效项
|
|
239
|
-
await adapter.set("valid1", {
|
|
240
|
-
value: "valid1",
|
|
241
|
-
expiresAt: now + 10000,
|
|
242
|
-
createdAt: now,
|
|
243
|
-
});
|
|
244
|
-
await adapter.set("valid2", {
|
|
245
|
-
value: "valid2",
|
|
246
|
-
expiresAt: now + 20000,
|
|
247
|
-
createdAt: now,
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
// 执行清理
|
|
251
|
-
const cleaned = await adapter.cleanupExpired!();
|
|
252
|
-
expect(cleaned).toBe(2);
|
|
253
|
-
|
|
254
|
-
// 验证过期项已被删除
|
|
255
|
-
const expired1 = await adapter.get("expired1");
|
|
256
|
-
const expired2 = await adapter.get("expired2");
|
|
257
|
-
expect(expired1).toBeNull();
|
|
258
|
-
expect(expired2).toBeNull();
|
|
259
|
-
|
|
260
|
-
// 验证有效项仍然存在
|
|
261
|
-
const valid1 = await adapter.get("valid1");
|
|
262
|
-
const valid2 = await adapter.get("valid2");
|
|
263
|
-
expect(valid1).not.toBeNull();
|
|
264
|
-
expect(valid2).not.toBeNull();
|
|
265
|
-
|
|
266
|
-
// 验证统计信息
|
|
267
|
-
const stats = await adapter.getStats();
|
|
268
|
-
expect(stats.size).toBe(2);
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it("清理时如果没有过期项应该返回 0", async () => {
|
|
272
|
-
await adapter.set("valid1", {
|
|
273
|
-
value: "valid1",
|
|
274
|
-
expiresAt: Date.now() + 10000,
|
|
275
|
-
createdAt: Date.now(),
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
const cleaned = await adapter.cleanupExpired!();
|
|
279
|
-
expect(cleaned).toBe(0);
|
|
280
|
-
|
|
281
|
-
const stats = await adapter.getStats();
|
|
282
|
-
expect(stats.size).toBe(1);
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
describe("RedisCacheAdapter", () => {
|
|
288
|
-
let adapter: RedisCacheAdapter;
|
|
289
|
-
let mockClient: {
|
|
290
|
-
storage: Map<string, { value: string; expiresAt?: number }>;
|
|
291
|
-
get(key: string): Promise<string | null>;
|
|
292
|
-
set(
|
|
293
|
-
key: string,
|
|
294
|
-
value: string,
|
|
295
|
-
expiryMode?: string,
|
|
296
|
-
time?: number
|
|
297
|
-
): Promise<string | null>;
|
|
298
|
-
del(key: string): Promise<number>;
|
|
299
|
-
keys(pattern: string): Promise<string[]>;
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
beforeEach(() => {
|
|
303
|
-
mockClient = {
|
|
304
|
-
storage: new Map(),
|
|
305
|
-
async get(key: string): Promise<string | null> {
|
|
306
|
-
const item = this.storage.get(key);
|
|
307
|
-
if (!item) {
|
|
308
|
-
return null;
|
|
309
|
-
}
|
|
310
|
-
// 检查是否过期
|
|
311
|
-
if (item.expiresAt && item.expiresAt <= Date.now()) {
|
|
312
|
-
this.storage.delete(key);
|
|
313
|
-
return null;
|
|
314
|
-
}
|
|
315
|
-
return item.value;
|
|
316
|
-
},
|
|
317
|
-
async set(
|
|
318
|
-
key: string,
|
|
319
|
-
value: string,
|
|
320
|
-
expiryMode?: string,
|
|
321
|
-
time?: number
|
|
322
|
-
): Promise<string | null> {
|
|
323
|
-
const expiresAt =
|
|
324
|
-
expiryMode === "EX" && time ? Date.now() + time * 1000 : undefined;
|
|
325
|
-
this.storage.set(key, { value, expiresAt });
|
|
326
|
-
return "OK";
|
|
327
|
-
},
|
|
328
|
-
async del(key: string): Promise<number> {
|
|
329
|
-
return this.storage.delete(key) ? 1 : 0;
|
|
330
|
-
},
|
|
331
|
-
async keys(pattern: string): Promise<string[]> {
|
|
332
|
-
const prefix = pattern.replace("*", "");
|
|
333
|
-
return Array.from(this.storage.keys()).filter((key) =>
|
|
334
|
-
key.startsWith(prefix)
|
|
335
|
-
);
|
|
336
|
-
},
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
adapter = new RedisCacheAdapter({ client: mockClient });
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
describe("基本操作", () => {
|
|
343
|
-
it("应该能够设置和获取缓存项", async () => {
|
|
344
|
-
const item: CacheItem<string> = {
|
|
345
|
-
value: "test-value",
|
|
346
|
-
expiresAt: Date.now() + 10000,
|
|
347
|
-
createdAt: Date.now(),
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
await adapter.set("test-key", item);
|
|
351
|
-
const result = await adapter.get("test-key");
|
|
352
|
-
|
|
353
|
-
expect(result).not.toBeNull();
|
|
354
|
-
expect(result?.value).toBe("test-value");
|
|
355
|
-
expect(result?.expiresAt).toBe(item.expiresAt);
|
|
356
|
-
expect(result?.createdAt).toBe(item.createdAt);
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
it("应该返回 null 当键不存在时", async () => {
|
|
360
|
-
const result = await adapter.get("non-existent-key");
|
|
361
|
-
expect(result).toBeNull();
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it("应该能够删除缓存项", async () => {
|
|
365
|
-
const item: CacheItem<string> = {
|
|
366
|
-
value: "test-value",
|
|
367
|
-
expiresAt: Date.now() + 10000,
|
|
368
|
-
createdAt: Date.now(),
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
await adapter.set("test-key", item);
|
|
372
|
-
const deleted = await adapter.delete("test-key");
|
|
373
|
-
expect(deleted).toBe(true);
|
|
374
|
-
|
|
375
|
-
const result = await adapter.get("test-key");
|
|
376
|
-
expect(result).toBeNull();
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
it("删除不存在的键应该返回 false", async () => {
|
|
380
|
-
const deleted = await adapter.delete("non-existent-key");
|
|
381
|
-
expect(deleted).toBe(false);
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
it("应该能够清空所有缓存", async () => {
|
|
385
|
-
await adapter.set("key1", {
|
|
386
|
-
value: "value1",
|
|
387
|
-
expiresAt: Date.now() + 10000,
|
|
388
|
-
createdAt: Date.now(),
|
|
389
|
-
});
|
|
390
|
-
await adapter.set("key2", {
|
|
391
|
-
value: "value2",
|
|
392
|
-
expiresAt: Date.now() + 10000,
|
|
393
|
-
createdAt: Date.now(),
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
await adapter.clear();
|
|
397
|
-
|
|
398
|
-
const stats = await adapter.getStats();
|
|
399
|
-
expect(stats.size).toBe(0);
|
|
400
|
-
});
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
describe("键前缀", () => {
|
|
404
|
-
it("应该使用默认前缀 'cache:'", async () => {
|
|
405
|
-
const item: CacheItem<string> = {
|
|
406
|
-
value: "test-value",
|
|
407
|
-
expiresAt: Date.now() + 10000,
|
|
408
|
-
createdAt: Date.now(),
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
await adapter.set("test-key", item);
|
|
412
|
-
|
|
413
|
-
// 检查 Redis 存储中是否有带前缀的键
|
|
414
|
-
const keys = await mockClient.keys("cache:*");
|
|
415
|
-
expect(keys).toContain("cache:test-key");
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
it("应该支持自定义前缀", async () => {
|
|
419
|
-
const customAdapter = new RedisCacheAdapter({
|
|
420
|
-
client: mockClient,
|
|
421
|
-
keyPrefix: "myapp:cache:",
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
const item: CacheItem<string> = {
|
|
425
|
-
value: "test-value",
|
|
426
|
-
expiresAt: Date.now() + 10000,
|
|
427
|
-
createdAt: Date.now(),
|
|
428
|
-
};
|
|
429
|
-
|
|
430
|
-
await customAdapter.set("test-key", item);
|
|
431
|
-
|
|
432
|
-
const keys = await mockClient.keys("myapp:cache:*");
|
|
433
|
-
expect(keys).toContain("myapp:cache:test-key");
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
it("keys() 应该返回不带前缀的键", async () => {
|
|
437
|
-
await adapter.set("key1", {
|
|
438
|
-
value: "value1",
|
|
439
|
-
expiresAt: Date.now() + 10000,
|
|
440
|
-
createdAt: Date.now(),
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
const keys = await adapter.keys();
|
|
444
|
-
expect(keys).toContain("key1");
|
|
445
|
-
expect(keys.every((key) => !key.startsWith("cache:"))).toBe(true);
|
|
446
|
-
});
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
describe("过期处理", () => {
|
|
450
|
-
it("应该自动删除过期的缓存项", async () => {
|
|
451
|
-
const expiredItem: CacheItem<string> = {
|
|
452
|
-
value: "expired-value",
|
|
453
|
-
expiresAt: Date.now() - 1000, // 已过期
|
|
454
|
-
createdAt: Date.now() - 5000,
|
|
455
|
-
};
|
|
456
|
-
|
|
457
|
-
await adapter.set("expired-key", expiredItem);
|
|
458
|
-
const result = await adapter.get("expired-key");
|
|
459
|
-
|
|
460
|
-
expect(result).toBeNull();
|
|
461
|
-
});
|
|
462
|
-
|
|
463
|
-
it("应该保留未过期的缓存项", async () => {
|
|
464
|
-
const validItem: CacheItem<string> = {
|
|
465
|
-
value: "valid-value",
|
|
466
|
-
expiresAt: Date.now() + 10000, // 10秒后过期
|
|
467
|
-
createdAt: Date.now(),
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
await adapter.set("valid-key", validItem);
|
|
471
|
-
const result = await adapter.get("valid-key");
|
|
472
|
-
|
|
473
|
-
expect(result).not.toBeNull();
|
|
474
|
-
expect(result?.value).toBe("valid-value");
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
it("应该使用 Redis 的过期时间", async () => {
|
|
478
|
-
const item: CacheItem<string> = {
|
|
479
|
-
value: "test-value",
|
|
480
|
-
expiresAt: Date.now() + 5000, // 5秒后过期
|
|
481
|
-
createdAt: Date.now(),
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
await adapter.set("test-key", item);
|
|
485
|
-
|
|
486
|
-
// 验证 Redis 存储中的过期时间设置
|
|
487
|
-
const redisKey = "cache:test-key";
|
|
488
|
-
const stored = mockClient.storage.get(redisKey);
|
|
489
|
-
expect(stored).toBeDefined();
|
|
490
|
-
expect(stored?.expiresAt).toBeGreaterThan(Date.now());
|
|
491
|
-
});
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
describe("错误处理", () => {
|
|
495
|
-
it("应该处理 JSON 解析错误", async () => {
|
|
496
|
-
// 设置无效的 JSON 数据
|
|
497
|
-
await mockClient.set("cache:invalid-key", "invalid-json");
|
|
498
|
-
|
|
499
|
-
const result = await adapter.get("invalid-key");
|
|
500
|
-
expect(result).toBeNull();
|
|
501
|
-
|
|
502
|
-
// 验证无效数据已被删除
|
|
503
|
-
const exists = mockClient.storage.has("cache:invalid-key");
|
|
504
|
-
expect(exists).toBe(false);
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
it("应该处理 Redis 返回 null 的情况", async () => {
|
|
508
|
-
const result = await adapter.get("non-existent-key");
|
|
509
|
-
expect(result).toBeNull();
|
|
510
|
-
});
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
describe("统计信息", () => {
|
|
514
|
-
it("应该能够获取缓存统计信息", async () => {
|
|
515
|
-
const now = Date.now();
|
|
516
|
-
await adapter.set("key1", {
|
|
517
|
-
value: "value1",
|
|
518
|
-
expiresAt: now + 10000,
|
|
519
|
-
createdAt: now,
|
|
520
|
-
});
|
|
521
|
-
await adapter.set("key2", {
|
|
522
|
-
value: "value2",
|
|
523
|
-
expiresAt: now + 20000,
|
|
524
|
-
createdAt: now + 1000,
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
const stats = await adapter.getStats();
|
|
528
|
-
expect(stats.size).toBe(2);
|
|
529
|
-
expect(stats.entries).toHaveLength(2);
|
|
530
|
-
expect(stats.entries[0]).toHaveProperty("key");
|
|
531
|
-
expect(stats.entries[0]).toHaveProperty("expiresAt");
|
|
532
|
-
expect(stats.entries[0]).toHaveProperty("createdAt");
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
it("应该排除过期的项", async () => {
|
|
536
|
-
await adapter.set("valid-key", {
|
|
537
|
-
value: "valid",
|
|
538
|
-
expiresAt: Date.now() + 10000,
|
|
539
|
-
createdAt: Date.now(),
|
|
540
|
-
});
|
|
541
|
-
await adapter.set("expired-key", {
|
|
542
|
-
value: "expired",
|
|
543
|
-
expiresAt: Date.now() - 1000,
|
|
544
|
-
createdAt: Date.now() - 5000,
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
const stats = await adapter.getStats();
|
|
548
|
-
// 过期项在 get 时会被删除,所以统计中不包含
|
|
549
|
-
expect(stats.size).toBe(1);
|
|
550
|
-
expect(stats.entries[0].key).toBe("valid-key");
|
|
551
|
-
});
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
describe("类型支持", () => {
|
|
555
|
-
it("应该支持不同的数据类型", async () => {
|
|
556
|
-
// 字符串
|
|
557
|
-
await adapter.set("string-key", {
|
|
558
|
-
value: "string-value",
|
|
559
|
-
expiresAt: Date.now() + 10000,
|
|
560
|
-
createdAt: Date.now(),
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
// 数字
|
|
564
|
-
await adapter.set("number-key", {
|
|
565
|
-
value: 123,
|
|
566
|
-
expiresAt: Date.now() + 10000,
|
|
567
|
-
createdAt: Date.now(),
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
// 对象
|
|
571
|
-
await adapter.set("object-key", {
|
|
572
|
-
value: { name: "test", age: 25 },
|
|
573
|
-
expiresAt: Date.now() + 10000,
|
|
574
|
-
createdAt: Date.now(),
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
// 数组
|
|
578
|
-
await adapter.set("array-key", {
|
|
579
|
-
value: [1, 2, 3],
|
|
580
|
-
expiresAt: Date.now() + 10000,
|
|
581
|
-
createdAt: Date.now(),
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
const stringResult = await adapter.get<string>("string-key");
|
|
585
|
-
expect(stringResult?.value).toBe("string-value");
|
|
586
|
-
|
|
587
|
-
const numberResult = await adapter.get<number>("number-key");
|
|
588
|
-
expect(numberResult?.value).toBe(123);
|
|
589
|
-
|
|
590
|
-
const objectResult = await adapter.get<{ name: string; age: number }>(
|
|
591
|
-
"object-key"
|
|
592
|
-
);
|
|
593
|
-
expect(objectResult?.value).toEqual({ name: "test", age: 25 });
|
|
594
|
-
|
|
595
|
-
const arrayResult = await adapter.get<number[]>("array-key");
|
|
596
|
-
expect(arrayResult?.value).toEqual([1, 2, 3]);
|
|
597
|
-
});
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
describe("批量操作", () => {
|
|
601
|
-
it("应该能够批量清空缓存", async () => {
|
|
602
|
-
await adapter.set("key1", {
|
|
603
|
-
value: "value1",
|
|
604
|
-
expiresAt: Date.now() + 10000,
|
|
605
|
-
createdAt: Date.now(),
|
|
606
|
-
});
|
|
607
|
-
await adapter.set("key2", {
|
|
608
|
-
value: "value2",
|
|
609
|
-
expiresAt: Date.now() + 10000,
|
|
610
|
-
createdAt: Date.now(),
|
|
611
|
-
});
|
|
612
|
-
await adapter.set("key3", {
|
|
613
|
-
value: "value3",
|
|
614
|
-
expiresAt: Date.now() + 10000,
|
|
615
|
-
createdAt: Date.now(),
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
await adapter.clear();
|
|
619
|
-
|
|
620
|
-
const stats = await adapter.getStats();
|
|
621
|
-
expect(stats.size).toBe(0);
|
|
622
|
-
});
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
describe("定期清理", () => {
|
|
626
|
-
it("应该能够批量清理过期项", async () => {
|
|
627
|
-
const now = Date.now();
|
|
628
|
-
|
|
629
|
-
// 添加一些过期项
|
|
630
|
-
await adapter.set("expired1", {
|
|
631
|
-
value: "expired1",
|
|
632
|
-
expiresAt: now - 1000,
|
|
633
|
-
createdAt: now - 5000,
|
|
634
|
-
});
|
|
635
|
-
await adapter.set("expired2", {
|
|
636
|
-
value: "expired2",
|
|
637
|
-
expiresAt: now - 500,
|
|
638
|
-
createdAt: now - 3000,
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
// 添加一些有效项
|
|
642
|
-
await adapter.set("valid1", {
|
|
643
|
-
value: "valid1",
|
|
644
|
-
expiresAt: now + 10000,
|
|
645
|
-
createdAt: now,
|
|
646
|
-
});
|
|
647
|
-
await adapter.set("valid2", {
|
|
648
|
-
value: "valid2",
|
|
649
|
-
expiresAt: now + 20000,
|
|
650
|
-
createdAt: now,
|
|
651
|
-
});
|
|
652
|
-
|
|
653
|
-
// 执行清理
|
|
654
|
-
const cleaned = await adapter.cleanupExpired!();
|
|
655
|
-
expect(cleaned).toBe(2);
|
|
656
|
-
|
|
657
|
-
// 验证过期项已被删除
|
|
658
|
-
const expired1 = await adapter.get("expired1");
|
|
659
|
-
const expired2 = await adapter.get("expired2");
|
|
660
|
-
expect(expired1).toBeNull();
|
|
661
|
-
expect(expired2).toBeNull();
|
|
662
|
-
|
|
663
|
-
// 验证有效项仍然存在
|
|
664
|
-
const valid1 = await adapter.get("valid1");
|
|
665
|
-
const valid2 = await adapter.get("valid2");
|
|
666
|
-
expect(valid1).not.toBeNull();
|
|
667
|
-
expect(valid2).not.toBeNull();
|
|
668
|
-
|
|
669
|
-
// 验证统计信息
|
|
670
|
-
const stats = await adapter.getStats();
|
|
671
|
-
expect(stats.size).toBe(2);
|
|
672
|
-
});
|
|
673
|
-
|
|
674
|
-
it("清理时如果没有过期项应该返回 0", async () => {
|
|
675
|
-
await adapter.set("valid1", {
|
|
676
|
-
value: "valid1",
|
|
677
|
-
expiresAt: Date.now() + 10000,
|
|
678
|
-
createdAt: Date.now(),
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
const cleaned = await adapter.cleanupExpired!();
|
|
682
|
-
expect(cleaned).toBe(0);
|
|
683
|
-
|
|
684
|
-
const stats = await adapter.getStats();
|
|
685
|
-
expect(stats.size).toBe(1);
|
|
686
|
-
});
|
|
687
|
-
});
|
|
688
|
-
});
|
|
689
|
-
});
|