befly 3.10.0 → 3.10.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/.gitignore +0 -0
- package/README.md +10 -13
- package/configs/presetFields.ts +10 -0
- package/configs/presetRegexp.ts +225 -0
- package/docs/README.md +17 -11
- package/docs/api/api.md +15 -1
- package/docs/guide/quickstart.md +19 -5
- package/docs/infra/redis.md +23 -11
- package/docs/quickstart.md +5 -335
- package/docs/reference/addon.md +0 -15
- package/docs/reference/config.md +1 -1
- package/docs/reference/logger.md +3 -3
- package/docs/reference/sync.md +99 -73
- package/docs/reference/table.md +1 -1
- package/package.json +15 -16
- package/docs/cipher.md +0 -582
- package/docs/database.md +0 -1176
- package/tests/_mocks/mockSqliteDb.ts +0 -204
- package/tests/addonHelper-cache.test.ts +0 -32
- package/tests/api-integration-array-number.test.ts +0 -282
- package/tests/apiHandler-routePath-only.test.ts +0 -32
- package/tests/befly-config-env.test.ts +0 -78
- package/tests/cacheHelper.test.ts +0 -323
- package/tests/cacheKeys.test.ts +0 -41
- package/tests/checkApi-routePath-strict.test.ts +0 -166
- package/tests/checkMenu.test.ts +0 -346
- package/tests/checkTable-smoke.test.ts +0 -157
- package/tests/cipher.test.ts +0 -249
- package/tests/dbDialect-cache.test.ts +0 -23
- package/tests/dbDialect.test.ts +0 -46
- package/tests/dbHelper-advanced.test.ts +0 -723
- package/tests/dbHelper-all-array-types.test.ts +0 -316
- package/tests/dbHelper-array-serialization.test.ts +0 -258
- package/tests/dbHelper-batch-write.test.ts +0 -90
- package/tests/dbHelper-columns.test.ts +0 -234
- package/tests/dbHelper-execute.test.ts +0 -187
- package/tests/dbHelper-joins.test.ts +0 -221
- package/tests/fields-redis-cache.test.ts +0 -127
- package/tests/fields-validate.test.ts +0 -99
- package/tests/fixtures/scanFilesAddon/node_modules/@befly-addon/demo/apis/sub/b.ts +0 -3
- package/tests/fixtures/scanFilesApis/a.ts +0 -3
- package/tests/fixtures/scanFilesApis/sub/b.ts +0 -3
- package/tests/getClientIp.test.ts +0 -54
- package/tests/integration.test.ts +0 -189
- package/tests/jwt.test.ts +0 -65
- package/tests/loadPlugins-order-smoke.test.ts +0 -75
- package/tests/logger.test.ts +0 -325
- package/tests/redisHelper.test.ts +0 -495
- package/tests/redisKeys.test.ts +0 -9
- package/tests/scanConfig.test.ts +0 -144
- package/tests/scanFiles-routePath.test.ts +0 -46
- package/tests/smoke-sql.test.ts +0 -24
- package/tests/sqlBuilder-advanced.test.ts +0 -608
- package/tests/sqlBuilder.test.ts +0 -209
- package/tests/sync-connection.test.ts +0 -183
- package/tests/sync-init-guard.test.ts +0 -105
- package/tests/syncApi-insBatch-fields-consistent.test.ts +0 -61
- package/tests/syncApi-obsolete-records.test.ts +0 -69
- package/tests/syncApi-type-compat.test.ts +0 -72
- package/tests/syncDev-permissions.test.ts +0 -81
- package/tests/syncMenu-disableMenus-hard-delete.test.ts +0 -88
- package/tests/syncMenu-duplicate-path.test.ts +0 -122
- package/tests/syncMenu-obsolete-records.test.ts +0 -161
- package/tests/syncMenu-parentPath-from-tree.test.ts +0 -75
- package/tests/syncMenu-paths.test.ts +0 -59
- package/tests/syncTable-apply.test.ts +0 -279
- package/tests/syncTable-array-number.test.ts +0 -160
- package/tests/syncTable-constants.test.ts +0 -101
- package/tests/syncTable-db-integration.test.ts +0 -237
- package/tests/syncTable-ddl.test.ts +0 -245
- package/tests/syncTable-helpers.test.ts +0 -99
- package/tests/syncTable-schema.test.ts +0 -99
- package/tests/syncTable-testkit.test.ts +0 -25
- package/tests/syncTable-types.test.ts +0 -122
- package/tests/tableRef-and-deserialize.test.ts +0 -67
- package/tests/util.test.ts +0 -100
- package/tests/validator-array-number.test.ts +0 -310
- package/tests/validator-default.test.ts +0 -373
- package/tests/validator.test.ts +0 -679
|
@@ -1,495 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RedisHelper 测试
|
|
3
|
-
* 测试 Redis 操作功能
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, expect, test, beforeAll, afterAll } from "bun:test";
|
|
7
|
-
|
|
8
|
-
import { Connect } from "../lib/connect.js";
|
|
9
|
-
import { RedisHelper } from "../lib/redisHelper.js";
|
|
10
|
-
|
|
11
|
-
let redis: RedisHelper;
|
|
12
|
-
|
|
13
|
-
beforeAll(async () => {
|
|
14
|
-
// 连接 Redis
|
|
15
|
-
await Connect.connectRedis({
|
|
16
|
-
host: "127.0.0.1",
|
|
17
|
-
port: 6379,
|
|
18
|
-
db: 0,
|
|
19
|
-
prefix: "befly:"
|
|
20
|
-
});
|
|
21
|
-
redis = new RedisHelper();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
afterAll(async () => {
|
|
25
|
-
// 断开 Redis 连接
|
|
26
|
-
await Connect.disconnectRedis();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe("RedisHelper - 字符串操作", () => {
|
|
30
|
-
test("setString - 设置字符串", async () => {
|
|
31
|
-
const result = await redis.setString("test:string", "Hello Redis");
|
|
32
|
-
expect(result).toBe("OK");
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test("getString - 获取字符串", async () => {
|
|
36
|
-
await redis.setString("test:string", "Hello Redis");
|
|
37
|
-
const value = await redis.getString("test:string");
|
|
38
|
-
expect(value).toBe("Hello Redis");
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test("getString - 获取不存在的键", async () => {
|
|
42
|
-
const value = await redis.getString("test:non_existent");
|
|
43
|
-
expect(value).toBeNull();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("setString - 设置带过期时间的字符串", async () => {
|
|
47
|
-
const result = await redis.setString("test:ttl", "Expire Test", 2);
|
|
48
|
-
expect(result).toBe("OK");
|
|
49
|
-
|
|
50
|
-
const value = await redis.getString("test:ttl");
|
|
51
|
-
expect(value).toBe("Expire Test");
|
|
52
|
-
|
|
53
|
-
// 等待过期
|
|
54
|
-
await new Promise((resolve) => setTimeout(resolve, 2100));
|
|
55
|
-
|
|
56
|
-
const expiredValue = await redis.getString("test:ttl");
|
|
57
|
-
expect(expiredValue).toBeNull();
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe("RedisHelper - 对象操作", () => {
|
|
62
|
-
test("setObject - 设置对象", async () => {
|
|
63
|
-
const obj = { name: "Test", age: 25, tags: ["a", "b"] };
|
|
64
|
-
const result = await redis.setObject("test:object", obj);
|
|
65
|
-
expect(result).toBe("OK");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("getObject - 获取对象", async () => {
|
|
69
|
-
const obj = { name: "Test", age: 25, tags: ["a", "b"] };
|
|
70
|
-
await redis.setObject("test:object", obj);
|
|
71
|
-
|
|
72
|
-
const value = await redis.getObject<any>("test:object");
|
|
73
|
-
expect(value).toEqual(obj);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("getObject - 获取不存在的对象", async () => {
|
|
77
|
-
const value = await redis.getObject("test:non_existent_obj");
|
|
78
|
-
expect(value).toBeNull();
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test("setObject - 设置带过期时间的对象", async () => {
|
|
82
|
-
const obj = { data: "test" };
|
|
83
|
-
const result = await redis.setObject("test:object:ttl", obj, 1);
|
|
84
|
-
expect(result).toBe("OK");
|
|
85
|
-
|
|
86
|
-
await new Promise((resolve) => setTimeout(resolve, 1100));
|
|
87
|
-
|
|
88
|
-
const expiredValue = await redis.getObject("test:object:ttl");
|
|
89
|
-
expect(expiredValue).toBeNull();
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test("delObject - 删除对象", async () => {
|
|
93
|
-
await redis.setObject("test:delete", { data: "test" });
|
|
94
|
-
await redis.delObject("test:delete");
|
|
95
|
-
|
|
96
|
-
const value = await redis.getObject("test:delete");
|
|
97
|
-
expect(value).toBeNull();
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe("RedisHelper - Set 操作", () => {
|
|
102
|
-
test("sadd - 添加成员到 Set", async () => {
|
|
103
|
-
// 先清除,确保测试隔离
|
|
104
|
-
await redis.del("test:set:sadd");
|
|
105
|
-
const count = await redis.sadd("test:set:sadd", ["member1", "member2", "member3"]);
|
|
106
|
-
expect(count).toBeGreaterThan(0);
|
|
107
|
-
await redis.del("test:set:sadd");
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test("sismember - 检查成员是否存在", async () => {
|
|
111
|
-
await redis.del("test:set");
|
|
112
|
-
await redis.sadd("test:set", ["member1"]);
|
|
113
|
-
|
|
114
|
-
const exists = await redis.sismember("test:set", "member1");
|
|
115
|
-
expect(exists).toBe(true);
|
|
116
|
-
|
|
117
|
-
const notExists = await redis.sismember("test:set", "non_existent");
|
|
118
|
-
expect(notExists).toBe(false);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test("scard - 获取 Set 成员数量", async () => {
|
|
122
|
-
await redis.del("test:set:count");
|
|
123
|
-
await redis.sadd("test:set:count", ["m1", "m2", "m3"]);
|
|
124
|
-
|
|
125
|
-
const count = await redis.scard("test:set:count");
|
|
126
|
-
expect(count).toBe(3);
|
|
127
|
-
|
|
128
|
-
await redis.del("test:set:count");
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test("smembers - 获取所有成员", async () => {
|
|
132
|
-
await redis.del("test:set:members");
|
|
133
|
-
await redis.sadd("test:set:members", ["a", "b", "c"]);
|
|
134
|
-
|
|
135
|
-
const members = await redis.smembers("test:set:members");
|
|
136
|
-
expect(members.length).toBe(3);
|
|
137
|
-
expect(members).toContain("a");
|
|
138
|
-
expect(members).toContain("b");
|
|
139
|
-
expect(members).toContain("c");
|
|
140
|
-
|
|
141
|
-
await redis.del("test:set:members");
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test("saddBatch - 批量向多个 Set 添加成员", async () => {
|
|
145
|
-
const count = await redis.saddBatch([
|
|
146
|
-
{ key: "test:saddBatch:1", members: ["a", "b"] },
|
|
147
|
-
{ key: "test:saddBatch:2", members: ["c", "d", "e"] }
|
|
148
|
-
]);
|
|
149
|
-
expect(count).toBe(5);
|
|
150
|
-
|
|
151
|
-
// 验证
|
|
152
|
-
const members1 = await redis.smembers("test:saddBatch:1");
|
|
153
|
-
const members2 = await redis.smembers("test:saddBatch:2");
|
|
154
|
-
expect(members1.length).toBe(2);
|
|
155
|
-
expect(members2.length).toBe(3);
|
|
156
|
-
|
|
157
|
-
// 清理
|
|
158
|
-
await redis.delBatch(["test:saddBatch:1", "test:saddBatch:2"]);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test("saddBatch - 空数组返回 0", async () => {
|
|
162
|
-
const count = await redis.saddBatch([]);
|
|
163
|
-
expect(count).toBe(0);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test("sismemberBatch - 批量检查成员是否存在", async () => {
|
|
167
|
-
await redis.sadd("test:sismemberBatch", ["a", "b", "c"]);
|
|
168
|
-
|
|
169
|
-
const results = await redis.sismemberBatch([
|
|
170
|
-
{ key: "test:sismemberBatch", member: "a" },
|
|
171
|
-
{ key: "test:sismemberBatch", member: "b" },
|
|
172
|
-
{ key: "test:sismemberBatch", member: "x" }
|
|
173
|
-
]);
|
|
174
|
-
expect(results).toEqual([true, true, false]);
|
|
175
|
-
|
|
176
|
-
// 清理
|
|
177
|
-
await redis.del("test:sismemberBatch");
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test("sismemberBatch - 空数组返回空数组", async () => {
|
|
181
|
-
const results = await redis.sismemberBatch([]);
|
|
182
|
-
expect(results).toEqual([]);
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
describe("RedisHelper - 键操作", () => {
|
|
187
|
-
test("exists - 检查键是否存在", async () => {
|
|
188
|
-
await redis.setString("test:exists", "value");
|
|
189
|
-
|
|
190
|
-
const exists = await redis.exists("test:exists");
|
|
191
|
-
expect(exists).toBe(true);
|
|
192
|
-
|
|
193
|
-
const notExists = await redis.exists("test:not_exists");
|
|
194
|
-
expect(notExists).toBe(false);
|
|
195
|
-
|
|
196
|
-
await redis.del("test:exists");
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test("expire - 设置过期时间", async () => {
|
|
200
|
-
await redis.setString("test:expire", "value");
|
|
201
|
-
const result = await redis.expire("test:expire", 1);
|
|
202
|
-
expect(result).toBe(1);
|
|
203
|
-
|
|
204
|
-
await new Promise((resolve) => setTimeout(resolve, 1100));
|
|
205
|
-
|
|
206
|
-
const value = await redis.getString("test:expire");
|
|
207
|
-
expect(value).toBeNull();
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
test("ttl - 获取剩余过期时间", async () => {
|
|
211
|
-
await redis.setString("test:ttl:check", "value", 10);
|
|
212
|
-
|
|
213
|
-
const ttl = await redis.ttl("test:ttl:check");
|
|
214
|
-
expect(ttl).toBeGreaterThan(0);
|
|
215
|
-
expect(ttl).toBeLessThanOrEqual(10);
|
|
216
|
-
|
|
217
|
-
await redis.del("test:ttl:check");
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
test("ttlBatch - 批量获取剩余过期时间", async () => {
|
|
221
|
-
await redis.setString("test:ttlBatch:1", "value1", 60);
|
|
222
|
-
await redis.setString("test:ttlBatch:2", "value2", 120);
|
|
223
|
-
// test:ttlBatch:3 不存在
|
|
224
|
-
|
|
225
|
-
const results = await redis.ttlBatch(["test:ttlBatch:1", "test:ttlBatch:2", "test:ttlBatch:3"]);
|
|
226
|
-
expect(results.length).toBe(3);
|
|
227
|
-
expect(results[0]).toBeGreaterThan(0);
|
|
228
|
-
expect(results[0]).toBeLessThanOrEqual(60);
|
|
229
|
-
expect(results[1]).toBeGreaterThan(0);
|
|
230
|
-
expect(results[1]).toBeLessThanOrEqual(120);
|
|
231
|
-
expect(results[2]).toBe(-2); // 键不存在
|
|
232
|
-
|
|
233
|
-
// 清理
|
|
234
|
-
await redis.delBatch(["test:ttlBatch:1", "test:ttlBatch:2"]);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
test("ttlBatch - 空数组返回空数组", async () => {
|
|
238
|
-
const results = await redis.ttlBatch([]);
|
|
239
|
-
expect(results).toEqual([]);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
test("ttlBatch - 无过期时间返回 -1", async () => {
|
|
243
|
-
await redis.setString("test:ttlBatch:nox", "value"); // 无 TTL
|
|
244
|
-
|
|
245
|
-
const results = await redis.ttlBatch(["test:ttlBatch:nox"]);
|
|
246
|
-
expect(results[0]).toBe(-1);
|
|
247
|
-
|
|
248
|
-
// 清理
|
|
249
|
-
await redis.del("test:ttlBatch:nox");
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
test("del - 删除键", async () => {
|
|
253
|
-
await redis.setString("test:delete:key", "value");
|
|
254
|
-
|
|
255
|
-
const count = await redis.del("test:delete:key");
|
|
256
|
-
expect(count).toBe(1);
|
|
257
|
-
|
|
258
|
-
const value = await redis.getString("test:delete:key");
|
|
259
|
-
expect(value).toBeNull();
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
test("delBatch - 批量删除键", async () => {
|
|
263
|
-
// 创建多个键
|
|
264
|
-
await redis.setString("test:batch:1", "value1");
|
|
265
|
-
await redis.setString("test:batch:2", "value2");
|
|
266
|
-
await redis.setString("test:batch:3", "value3");
|
|
267
|
-
|
|
268
|
-
// 批量删除
|
|
269
|
-
const count = await redis.delBatch(["test:batch:1", "test:batch:2", "test:batch:3"]);
|
|
270
|
-
expect(count).toBe(3);
|
|
271
|
-
|
|
272
|
-
// 验证删除成功
|
|
273
|
-
const value1 = await redis.getString("test:batch:1");
|
|
274
|
-
const value2 = await redis.getString("test:batch:2");
|
|
275
|
-
const value3 = await redis.getString("test:batch:3");
|
|
276
|
-
expect(value1).toBeNull();
|
|
277
|
-
expect(value2).toBeNull();
|
|
278
|
-
expect(value3).toBeNull();
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
test("delBatch - 空数组返回 0", async () => {
|
|
282
|
-
const count = await redis.delBatch([]);
|
|
283
|
-
expect(count).toBe(0);
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
test("delBatch - 删除不存在的键返回 0", async () => {
|
|
287
|
-
const count = await redis.delBatch(["test:non:existent:1", "test:non:existent:2"]);
|
|
288
|
-
expect(count).toBe(0);
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
test("delBatch - 部分存在的键", async () => {
|
|
292
|
-
await redis.setString("test:partial:1", "value1");
|
|
293
|
-
await redis.setString("test:partial:2", "value2");
|
|
294
|
-
// test:partial:3 不存在
|
|
295
|
-
|
|
296
|
-
const count = await redis.delBatch(["test:partial:1", "test:partial:2", "test:partial:3"]);
|
|
297
|
-
expect(count).toBe(2); // 只有 2 个键被删除
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
test("setBatch - 批量设置对象", async () => {
|
|
301
|
-
const items = [
|
|
302
|
-
{ key: "test:setBatch:1", value: { name: "Alice" } },
|
|
303
|
-
{ key: "test:setBatch:2", value: { name: "Bob" } },
|
|
304
|
-
{ key: "test:setBatch:3", value: { name: "Charlie" } }
|
|
305
|
-
];
|
|
306
|
-
|
|
307
|
-
const count = await redis.setBatch(items);
|
|
308
|
-
expect(count).toBe(3);
|
|
309
|
-
|
|
310
|
-
// 验证设置成功
|
|
311
|
-
const value1 = await redis.getObject("test:setBatch:1");
|
|
312
|
-
const value2 = await redis.getObject("test:setBatch:2");
|
|
313
|
-
const value3 = await redis.getObject("test:setBatch:3");
|
|
314
|
-
expect(value1).toEqual({ name: "Alice" });
|
|
315
|
-
expect(value2).toEqual({ name: "Bob" });
|
|
316
|
-
expect(value3).toEqual({ name: "Charlie" });
|
|
317
|
-
|
|
318
|
-
// 清理
|
|
319
|
-
await redis.delBatch(["test:setBatch:1", "test:setBatch:2", "test:setBatch:3"]);
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
test("setBatch - 空数组返回 0", async () => {
|
|
323
|
-
const count = await redis.setBatch([]);
|
|
324
|
-
expect(count).toBe(0);
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
test("setBatch - 带 TTL 的批量设置", async () => {
|
|
328
|
-
const items = [
|
|
329
|
-
{ key: "test:setBatch:ttl:1", value: { data: 1 }, ttl: 10 },
|
|
330
|
-
{ key: "test:setBatch:ttl:2", value: { data: 2 }, ttl: 10 }
|
|
331
|
-
];
|
|
332
|
-
|
|
333
|
-
const count = await redis.setBatch(items);
|
|
334
|
-
expect(count).toBe(2);
|
|
335
|
-
|
|
336
|
-
// 验证 TTL 已设置
|
|
337
|
-
const ttl1 = await redis.ttl("test:setBatch:ttl:1");
|
|
338
|
-
const ttl2 = await redis.ttl("test:setBatch:ttl:2");
|
|
339
|
-
expect(ttl1).toBeGreaterThan(0);
|
|
340
|
-
expect(ttl1).toBeLessThanOrEqual(10);
|
|
341
|
-
expect(ttl2).toBeGreaterThan(0);
|
|
342
|
-
expect(ttl2).toBeLessThanOrEqual(10);
|
|
343
|
-
|
|
344
|
-
// 清理
|
|
345
|
-
await redis.delBatch(["test:setBatch:ttl:1", "test:setBatch:ttl:2"]);
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
test("getBatch - 批量获取对象", async () => {
|
|
349
|
-
// 设置测试数据
|
|
350
|
-
await redis.setObject("test:getBatch:1", { name: "Alice" });
|
|
351
|
-
await redis.setObject("test:getBatch:2", { name: "Bob" });
|
|
352
|
-
await redis.setObject("test:getBatch:3", { name: "Charlie" });
|
|
353
|
-
|
|
354
|
-
// 批量获取
|
|
355
|
-
const results = await redis.getBatch(["test:getBatch:1", "test:getBatch:2", "test:getBatch:3"]);
|
|
356
|
-
expect(results.length).toBe(3);
|
|
357
|
-
expect(results[0]).toEqual({ name: "Alice" });
|
|
358
|
-
expect(results[1]).toEqual({ name: "Bob" });
|
|
359
|
-
expect(results[2]).toEqual({ name: "Charlie" });
|
|
360
|
-
|
|
361
|
-
// 清理
|
|
362
|
-
await redis.delBatch(["test:getBatch:1", "test:getBatch:2", "test:getBatch:3"]);
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
test("getBatch - 空数组返回空数组", async () => {
|
|
366
|
-
const results = await redis.getBatch([]);
|
|
367
|
-
expect(results).toEqual([]);
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
test("getBatch - 不存在的键返回 null", async () => {
|
|
371
|
-
const results = await redis.getBatch(["test:non:existent:a", "test:non:existent:b"]);
|
|
372
|
-
expect(results).toEqual([null, null]);
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
test("getBatch - 部分存在的键", async () => {
|
|
376
|
-
await redis.setObject("test:partial:a", { data: "a" });
|
|
377
|
-
// test:partial:b 不存在
|
|
378
|
-
|
|
379
|
-
const results = await redis.getBatch(["test:partial:a", "test:partial:b"]);
|
|
380
|
-
expect(results.length).toBe(2);
|
|
381
|
-
expect(results[0]).toEqual({ data: "a" });
|
|
382
|
-
expect(results[1]).toBeNull();
|
|
383
|
-
|
|
384
|
-
// 清理
|
|
385
|
-
await redis.del("test:partial:a");
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
test("existsBatch - 批量检查键是否存在", async () => {
|
|
389
|
-
await redis.setString("test:existsBatch:1", "value1");
|
|
390
|
-
await redis.setString("test:existsBatch:2", "value2");
|
|
391
|
-
// test:existsBatch:3 不存在
|
|
392
|
-
|
|
393
|
-
const results = await redis.existsBatch(["test:existsBatch:1", "test:existsBatch:2", "test:existsBatch:3"]);
|
|
394
|
-
expect(results).toEqual([true, true, false]);
|
|
395
|
-
|
|
396
|
-
// 清理
|
|
397
|
-
await redis.delBatch(["test:existsBatch:1", "test:existsBatch:2"]);
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
test("existsBatch - 空数组返回空数组", async () => {
|
|
401
|
-
const results = await redis.existsBatch([]);
|
|
402
|
-
expect(results).toEqual([]);
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
test("existsBatch - 全部不存在", async () => {
|
|
406
|
-
const results = await redis.existsBatch(["test:none:1", "test:none:2"]);
|
|
407
|
-
expect(results).toEqual([false, false]);
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
test("expireBatch - 批量设置过期时间", async () => {
|
|
411
|
-
await redis.setString("test:expireBatch:1", "value1");
|
|
412
|
-
await redis.setString("test:expireBatch:2", "value2");
|
|
413
|
-
|
|
414
|
-
const count = await redis.expireBatch([
|
|
415
|
-
{ key: "test:expireBatch:1", seconds: 60 },
|
|
416
|
-
{ key: "test:expireBatch:2", seconds: 120 }
|
|
417
|
-
]);
|
|
418
|
-
expect(count).toBe(2);
|
|
419
|
-
|
|
420
|
-
// 验证 TTL 已设置
|
|
421
|
-
const ttl1 = await redis.ttl("test:expireBatch:1");
|
|
422
|
-
const ttl2 = await redis.ttl("test:expireBatch:2");
|
|
423
|
-
expect(ttl1).toBeGreaterThan(0);
|
|
424
|
-
expect(ttl1).toBeLessThanOrEqual(60);
|
|
425
|
-
expect(ttl2).toBeGreaterThan(0);
|
|
426
|
-
expect(ttl2).toBeLessThanOrEqual(120);
|
|
427
|
-
|
|
428
|
-
// 清理
|
|
429
|
-
await redis.delBatch(["test:expireBatch:1", "test:expireBatch:2"]);
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
test("expireBatch - 空数组返回 0", async () => {
|
|
433
|
-
const count = await redis.expireBatch([]);
|
|
434
|
-
expect(count).toBe(0);
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
test("expireBatch - 不存在的键返回 0", async () => {
|
|
438
|
-
const count = await redis.expireBatch([
|
|
439
|
-
{ key: "test:expire:none:1", seconds: 60 },
|
|
440
|
-
{ key: "test:expire:none:2", seconds: 60 }
|
|
441
|
-
]);
|
|
442
|
-
expect(count).toBe(0);
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
test("expireBatch - 部分存在的键", async () => {
|
|
446
|
-
await redis.setString("test:expire:partial:1", "value1");
|
|
447
|
-
// test:expire:partial:2 不存在
|
|
448
|
-
|
|
449
|
-
const count = await redis.expireBatch([
|
|
450
|
-
{ key: "test:expire:partial:1", seconds: 60 },
|
|
451
|
-
{ key: "test:expire:partial:2", seconds: 60 }
|
|
452
|
-
]);
|
|
453
|
-
expect(count).toBe(1);
|
|
454
|
-
|
|
455
|
-
// 清理
|
|
456
|
-
await redis.del("test:expire:partial:1");
|
|
457
|
-
});
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
describe("RedisHelper - ID 生成", () => {
|
|
461
|
-
test("genTimeID - 生成唯一 ID", async () => {
|
|
462
|
-
const id1 = await redis.genTimeID();
|
|
463
|
-
const id2 = await redis.genTimeID();
|
|
464
|
-
|
|
465
|
-
expect(typeof id1).toBe("number");
|
|
466
|
-
expect(typeof id2).toBe("number");
|
|
467
|
-
expect(id1).not.toBe(id2);
|
|
468
|
-
expect(id1.toString().length).toBe(16);
|
|
469
|
-
|
|
470
|
-
// 验证后缀在 100-999 范围内
|
|
471
|
-
const suffix1 = id1 % 1000;
|
|
472
|
-
const suffix2 = id2 % 1000;
|
|
473
|
-
expect(suffix1).toBeGreaterThanOrEqual(100);
|
|
474
|
-
expect(suffix1).toBeLessThan(1000);
|
|
475
|
-
expect(suffix2).toBeGreaterThanOrEqual(100);
|
|
476
|
-
expect(suffix2).toBeLessThan(1000);
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
test("genTimeID - 多次生成保持唯一", async () => {
|
|
480
|
-
const ids: number[] = [];
|
|
481
|
-
for (let i = 0; i < 10; i++) {
|
|
482
|
-
ids.push(await redis.genTimeID());
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
const uniqueIds = new Set(ids);
|
|
486
|
-
expect(uniqueIds.size).toBe(10);
|
|
487
|
-
});
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
describe("RedisHelper - 连接测试", () => {
|
|
491
|
-
test("ping - 测试连接", async () => {
|
|
492
|
-
const result = await redis.ping();
|
|
493
|
-
expect(result).toBe("PONG");
|
|
494
|
-
});
|
|
495
|
-
});
|
package/tests/redisKeys.test.ts
DELETED
package/tests/scanConfig.test.ts
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test, beforeAll, afterAll, beforeEach } from "bun:test";
|
|
2
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
|
|
5
|
-
import { scanConfig } from "../utils/scanConfig";
|
|
6
|
-
|
|
7
|
-
const tempRootDir = join(process.cwd(), "temp", "test-scan-config");
|
|
8
|
-
|
|
9
|
-
function writeJson(filePath: string, data: Record<string, any>) {
|
|
10
|
-
const json = JSON.stringify(data, null, 4);
|
|
11
|
-
writeFileSync(filePath, json, { encoding: "utf8" });
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function createCaseDirs(): { caseRootDir: string; dirA: string; dirB: string } {
|
|
15
|
-
const caseRootDir = join(tempRootDir, `case-${Date.now()}-${Math.random().toString(16).slice(2)}`);
|
|
16
|
-
const dirA = join(caseRootDir, "a");
|
|
17
|
-
const dirB = join(caseRootDir, "b");
|
|
18
|
-
|
|
19
|
-
if (!existsSync(dirA)) {
|
|
20
|
-
mkdirSync(dirA, { recursive: true });
|
|
21
|
-
}
|
|
22
|
-
if (!existsSync(dirB)) {
|
|
23
|
-
mkdirSync(dirB, { recursive: true });
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
caseRootDir: caseRootDir,
|
|
28
|
-
dirA: dirA,
|
|
29
|
-
dirB: dirB
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
afterAll(() => {
|
|
34
|
-
if (existsSync(tempRootDir)) {
|
|
35
|
-
rmSync(tempRootDir, { recursive: true, force: true });
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe("utils - scanConfig", () => {
|
|
40
|
-
beforeAll(() => {
|
|
41
|
-
if (!existsSync(tempRootDir)) {
|
|
42
|
-
mkdirSync(tempRootDir, { recursive: true });
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
beforeEach(() => {
|
|
47
|
-
// 无需清空:每个用例使用唯一目录,避免 import cache 干扰
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("mode=first:返回搜索到的第一个配置(按 dirs 顺序)", async () => {
|
|
51
|
-
const { caseRootDir, dirA, dirB } = createCaseDirs();
|
|
52
|
-
|
|
53
|
-
writeJson(join(dirA, "cfg.json"), {
|
|
54
|
-
foo: 1,
|
|
55
|
-
database: { host: "a" }
|
|
56
|
-
});
|
|
57
|
-
writeJson(join(dirB, "cfg.json"), {
|
|
58
|
-
foo: 2,
|
|
59
|
-
database: { host: "b" }
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
const config = await scanConfig({
|
|
63
|
-
cwd: caseRootDir,
|
|
64
|
-
dirs: ["a", "b"],
|
|
65
|
-
files: ["cfg"],
|
|
66
|
-
extensions: [".json"],
|
|
67
|
-
mode: "first"
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
expect(config).toEqual({
|
|
71
|
-
foo: 1,
|
|
72
|
-
database: { host: "a" }
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("mode=merge:按 defaults ← a ← b 合并(数组拼接,标量后者覆盖)", async () => {
|
|
77
|
-
const { caseRootDir, dirA, dirB } = createCaseDirs();
|
|
78
|
-
|
|
79
|
-
writeJson(join(dirA, "cfg.json"), {
|
|
80
|
-
foo: 1,
|
|
81
|
-
menus: ["a"],
|
|
82
|
-
database: { host: "a", port: 3306 },
|
|
83
|
-
nested: { a: 1 }
|
|
84
|
-
});
|
|
85
|
-
writeJson(join(dirB, "cfg.json"), {
|
|
86
|
-
foo: 2,
|
|
87
|
-
menus: ["b"],
|
|
88
|
-
database: { host: "b" },
|
|
89
|
-
nested: { b: 2 }
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const config = await scanConfig({
|
|
93
|
-
cwd: caseRootDir,
|
|
94
|
-
dirs: ["a", "b"],
|
|
95
|
-
files: ["cfg"],
|
|
96
|
-
extensions: [".json"],
|
|
97
|
-
mode: "merge",
|
|
98
|
-
defaults: {
|
|
99
|
-
foo: 0,
|
|
100
|
-
menus: ["default"],
|
|
101
|
-
database: { host: "default", port: 1111 },
|
|
102
|
-
nested: { z: 9 }
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// merge-anything 的 mergeAndConcat:数组拼接;对象深合并;标量后者覆盖
|
|
107
|
-
expect(config.foo).toBe(2);
|
|
108
|
-
expect(config.menus).toEqual(["default", "a", "b"]);
|
|
109
|
-
expect(config.database).toEqual({
|
|
110
|
-
host: "b",
|
|
111
|
-
port: 3306
|
|
112
|
-
});
|
|
113
|
-
expect(config.nested).toEqual({
|
|
114
|
-
z: 9,
|
|
115
|
-
a: 1,
|
|
116
|
-
b: 2
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test("paths:只返回指定路径字段", async () => {
|
|
121
|
-
const { caseRootDir, dirA } = createCaseDirs();
|
|
122
|
-
|
|
123
|
-
writeJson(join(dirA, "cfg.json"), {
|
|
124
|
-
foo: 1,
|
|
125
|
-
menus: ["a"],
|
|
126
|
-
database: { host: "a", port: 3306 },
|
|
127
|
-
secret: "should-not-return"
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const config = await scanConfig({
|
|
131
|
-
cwd: caseRootDir,
|
|
132
|
-
dirs: ["a"],
|
|
133
|
-
files: ["cfg"],
|
|
134
|
-
extensions: [".json"],
|
|
135
|
-
mode: "first",
|
|
136
|
-
paths: ["menus", "database.host", "not.exists"]
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
expect(config).toEqual({
|
|
140
|
-
menus: ["a"],
|
|
141
|
-
database: { host: "a" }
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
|
|
4
|
-
import { scanFiles } from "../utils/scanFiles.js";
|
|
5
|
-
|
|
6
|
-
describe("scanFiles - api routePath formatting", () => {
|
|
7
|
-
test("routePrefix 应为 /core|/app|/addon 且 routePath 不应出现 /api//", async () => {
|
|
8
|
-
const fixturesDir = fileURLToPath(new URL("./fixtures/scanFilesApis", import.meta.url));
|
|
9
|
-
const addonApisDir = fileURLToPath(new URL("./fixtures/scanFilesAddon/node_modules/@befly-addon/demo/apis", import.meta.url));
|
|
10
|
-
|
|
11
|
-
const coreApis = await scanFiles(fixturesDir, "core", "api", "**/*.ts");
|
|
12
|
-
const appApis = await scanFiles(fixturesDir, "app", "api", "**/*.ts");
|
|
13
|
-
const addonApis = await scanFiles(addonApisDir, "addon", "api", "**/*.ts");
|
|
14
|
-
|
|
15
|
-
const all = ([] as any[]).concat(coreApis as any, appApis as any, addonApis as any);
|
|
16
|
-
expect(all.length).toBeGreaterThan(0);
|
|
17
|
-
|
|
18
|
-
for (const api of all) {
|
|
19
|
-
expect(typeof api.routePrefix).toBe("string");
|
|
20
|
-
expect(typeof api.routePath).toBe("string");
|
|
21
|
-
|
|
22
|
-
if (api.source === "addon") {
|
|
23
|
-
expect(api.routePrefix.startsWith("/addon/")).toBe(true);
|
|
24
|
-
expect(typeof api.addonName).toBe("string");
|
|
25
|
-
expect(api.addonName.length > 0).toBe(true);
|
|
26
|
-
expect(api.routePrefix).toBe(`/addon/${api.addonName}`);
|
|
27
|
-
} else {
|
|
28
|
-
expect(["/core", "/app"].includes(api.routePrefix)).toBe(true);
|
|
29
|
-
}
|
|
30
|
-
expect(api.routePath.includes("/api//")).toBe(false);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const coreB = (coreApis as any[]).find((item) => item.relativePath === "sub/b");
|
|
34
|
-
expect(coreB.routePrefix).toBe("/core");
|
|
35
|
-
expect(coreB.routePath).toBe("/api/core/sub/b");
|
|
36
|
-
|
|
37
|
-
const appB = (appApis as any[]).find((item) => item.relativePath === "sub/b");
|
|
38
|
-
expect(appB.routePrefix).toBe("/app");
|
|
39
|
-
expect(appB.routePath).toBe("/api/app/sub/b");
|
|
40
|
-
|
|
41
|
-
const addonB = (addonApis as any[]).find((item) => item.relativePath === "sub/b");
|
|
42
|
-
expect(addonB.addonName).toBe("demo");
|
|
43
|
-
expect(addonB.routePrefix).toBe("/addon/demo");
|
|
44
|
-
expect(addonB.routePath).toBe("/api/addon/demo/sub/b");
|
|
45
|
-
});
|
|
46
|
-
});
|