befly 3.10.1 → 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.
Files changed (66) hide show
  1. package/.gitignore +0 -0
  2. package/configs/presetFields.ts +10 -0
  3. package/configs/presetRegexp.ts +225 -0
  4. package/package.json +15 -16
  5. package/tests/_mocks/mockSqliteDb.ts +0 -204
  6. package/tests/addonHelper-cache.test.ts +0 -32
  7. package/tests/api-integration-array-number.test.ts +0 -282
  8. package/tests/apiHandler-routePath-only.test.ts +0 -32
  9. package/tests/befly-config-env.test.ts +0 -78
  10. package/tests/cacheHelper.test.ts +0 -323
  11. package/tests/cacheKeys.test.ts +0 -41
  12. package/tests/checkApi-routePath-strict.test.ts +0 -166
  13. package/tests/checkMenu.test.ts +0 -346
  14. package/tests/checkTable-smoke.test.ts +0 -157
  15. package/tests/cipher.test.ts +0 -249
  16. package/tests/dbDialect-cache.test.ts +0 -23
  17. package/tests/dbDialect.test.ts +0 -46
  18. package/tests/dbHelper-advanced.test.ts +0 -723
  19. package/tests/dbHelper-all-array-types.test.ts +0 -316
  20. package/tests/dbHelper-array-serialization.test.ts +0 -258
  21. package/tests/dbHelper-batch-write.test.ts +0 -90
  22. package/tests/dbHelper-columns.test.ts +0 -234
  23. package/tests/dbHelper-execute.test.ts +0 -187
  24. package/tests/dbHelper-joins.test.ts +0 -221
  25. package/tests/fields-redis-cache.test.ts +0 -127
  26. package/tests/fields-validate.test.ts +0 -99
  27. package/tests/fixtures/scanFilesAddon/node_modules/@befly-addon/demo/apis/sub/b.ts +0 -3
  28. package/tests/fixtures/scanFilesApis/a.ts +0 -3
  29. package/tests/fixtures/scanFilesApis/sub/b.ts +0 -3
  30. package/tests/getClientIp.test.ts +0 -54
  31. package/tests/integration.test.ts +0 -189
  32. package/tests/jwt.test.ts +0 -65
  33. package/tests/loadPlugins-order-smoke.test.ts +0 -75
  34. package/tests/logger.test.ts +0 -325
  35. package/tests/redisHelper.test.ts +0 -495
  36. package/tests/redisKeys.test.ts +0 -9
  37. package/tests/scanConfig.test.ts +0 -144
  38. package/tests/scanFiles-routePath.test.ts +0 -46
  39. package/tests/smoke-sql.test.ts +0 -24
  40. package/tests/sqlBuilder-advanced.test.ts +0 -608
  41. package/tests/sqlBuilder.test.ts +0 -209
  42. package/tests/sync-connection.test.ts +0 -183
  43. package/tests/sync-init-guard.test.ts +0 -105
  44. package/tests/syncApi-insBatch-fields-consistent.test.ts +0 -61
  45. package/tests/syncApi-obsolete-records.test.ts +0 -69
  46. package/tests/syncApi-type-compat.test.ts +0 -72
  47. package/tests/syncDev-permissions.test.ts +0 -81
  48. package/tests/syncMenu-disableMenus-hard-delete.test.ts +0 -88
  49. package/tests/syncMenu-duplicate-path.test.ts +0 -122
  50. package/tests/syncMenu-obsolete-records.test.ts +0 -161
  51. package/tests/syncMenu-parentPath-from-tree.test.ts +0 -75
  52. package/tests/syncMenu-paths.test.ts +0 -59
  53. package/tests/syncTable-apply.test.ts +0 -279
  54. package/tests/syncTable-array-number.test.ts +0 -160
  55. package/tests/syncTable-constants.test.ts +0 -101
  56. package/tests/syncTable-db-integration.test.ts +0 -237
  57. package/tests/syncTable-ddl.test.ts +0 -245
  58. package/tests/syncTable-helpers.test.ts +0 -99
  59. package/tests/syncTable-schema.test.ts +0 -99
  60. package/tests/syncTable-testkit.test.ts +0 -25
  61. package/tests/syncTable-types.test.ts +0 -122
  62. package/tests/tableRef-and-deserialize.test.ts +0 -67
  63. package/tests/util.test.ts +0 -100
  64. package/tests/validator-array-number.test.ts +0 -310
  65. package/tests/validator-default.test.ts +0 -373
  66. package/tests/validator.test.ts +0 -679
@@ -1,323 +0,0 @@
1
- /**
2
- * CacheHelper 单元测试
3
- */
4
-
5
- import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test";
6
-
7
- import { CacheHelper } from "../lib/cacheHelper.js";
8
- import { CacheKeys } from "../lib/cacheKeys.js";
9
- import { setMockLogger } from "../lib/logger.js";
10
-
11
- // Mock pino logger
12
- const mockPino = {
13
- info: mock(() => {}),
14
- warn: mock(() => {}),
15
- error: mock(() => {}),
16
- debug: mock(() => {}),
17
- fatal: mock(() => {}),
18
- trace: mock(() => {}),
19
- silent: mock(() => {}),
20
- child: mock(() => mockPino),
21
- level: "info"
22
- };
23
-
24
- describe("CacheHelper", () => {
25
- let cacheHelper: CacheHelper;
26
- let mockDb: any;
27
- let mockRedis: any;
28
-
29
- beforeEach(() => {
30
- // 设置 mock logger
31
- setMockLogger(mockPino as any);
32
-
33
- // Mock 数据库方法
34
- mockDb = {
35
- tableExists: mock(() => Promise.resolve(true)),
36
- getAll: mock(() => Promise.resolve({ lists: [], total: 0 }))
37
- };
38
-
39
- // Mock Redis 方法
40
- mockRedis = {
41
- setObject: mock(() => Promise.resolve("OK")),
42
- getObject: mock(() => Promise.resolve(null)),
43
- sadd: mock(() => Promise.resolve(1)),
44
- saddBatch: mock(() => Promise.resolve(1)),
45
- smembers: mock(() => Promise.resolve([])),
46
- sismember: mock(() => Promise.resolve(0)),
47
- del: mock(() => Promise.resolve(1)),
48
- delBatch: mock(() => Promise.resolve(1)),
49
- exists: mock(() => Promise.resolve(true))
50
- };
51
-
52
- cacheHelper = new CacheHelper({ db: mockDb, redis: mockRedis });
53
- });
54
-
55
- afterEach(() => {
56
- // 重置 mock logger
57
- setMockLogger(null);
58
- });
59
-
60
- describe("cacheApis", () => {
61
- it("表不存在时跳过缓存", async () => {
62
- mockDb.tableExists = mock(() => Promise.resolve(false));
63
-
64
- await cacheHelper.cacheApis();
65
-
66
- expect(mockDb.tableExists).toHaveBeenCalledWith("addon_admin_api");
67
- expect(mockDb.getAll).not.toHaveBeenCalled();
68
- });
69
-
70
- it("正常缓存接口列表", async () => {
71
- const apis = [
72
- { id: 1, name: "登录", routePath: "/api/login" },
73
- { id: 2, name: "用户列表", routePath: "/api/user/list" }
74
- ];
75
- mockDb.getAll = mock(() => Promise.resolve({ lists: apis, total: apis.length }));
76
-
77
- await cacheHelper.cacheApis();
78
-
79
- expect(mockRedis.setObject).toHaveBeenCalledWith(CacheKeys.apisAll(), apis);
80
- });
81
-
82
- it("缓存失败时记录警告", async () => {
83
- mockRedis.setObject = mock(() => Promise.resolve(null));
84
-
85
- await cacheHelper.cacheApis();
86
-
87
- expect(mockPino.warn).toHaveBeenCalled();
88
- });
89
-
90
- it("异常时记录错误", async () => {
91
- mockDb.getAll = mock(() => Promise.reject(new Error("DB Error")));
92
-
93
- await cacheHelper.cacheApis();
94
-
95
- expect(mockPino.error).toHaveBeenCalled();
96
- });
97
- });
98
-
99
- describe("cacheMenus", () => {
100
- it("表不存在时跳过缓存", async () => {
101
- mockDb.tableExists = mock(() => Promise.resolve(false));
102
-
103
- await cacheHelper.cacheMenus();
104
-
105
- expect(mockDb.tableExists).toHaveBeenCalledWith("addon_admin_menu");
106
- expect(mockDb.getAll).not.toHaveBeenCalled();
107
- });
108
-
109
- it("正常缓存菜单列表", async () => {
110
- const menus = [
111
- { id: 1, pid: 0, name: "首页", path: "/home", sort: 1 },
112
- { id: 2, pid: 0, name: "用户管理", path: "/user", sort: 2 }
113
- ];
114
- mockDb.getAll = mock(() => Promise.resolve({ lists: menus, total: menus.length }));
115
-
116
- await cacheHelper.cacheMenus();
117
-
118
- expect(mockRedis.setObject).toHaveBeenCalledWith(CacheKeys.menusAll(), menus);
119
- });
120
- });
121
-
122
- describe("rebuildRoleApiPermissions", () => {
123
- it("表不存在时跳过缓存", async () => {
124
- mockDb.tableExists = mock((table: string) => {
125
- if (table === "addon_admin_role") return Promise.resolve(false);
126
- return Promise.resolve(false);
127
- });
128
-
129
- await cacheHelper.rebuildRoleApiPermissions();
130
-
131
- expect(mockPino.warn).toHaveBeenCalled();
132
- });
133
-
134
- it("正常重建角色权限(覆盖更新)", async () => {
135
- const roles = [
136
- { id: 1, code: "admin", apis: ["/api/login", "/api/user/list", "/api/user/del"] },
137
- { id: 2, code: "user", apis: ["/api/login"] }
138
- ];
139
-
140
- mockDb.getAll = mock((opts: any) => {
141
- if (opts.table === "addon_admin_role") return Promise.resolve({ lists: roles, total: roles.length });
142
- return Promise.resolve({ lists: [], total: 0 });
143
- });
144
-
145
- await cacheHelper.rebuildRoleApiPermissions();
146
-
147
- // 验证批量删除角色 key
148
- expect(mockRedis.delBatch).toHaveBeenCalledTimes(1);
149
- const delBatchArgs = (mockRedis.delBatch as any).mock.calls[0][0] as string[];
150
- expect(delBatchArgs).toEqual([CacheKeys.roleApis("admin"), CacheKeys.roleApis("user")]);
151
-
152
- // 验证批量写入
153
- expect(mockRedis.saddBatch).toHaveBeenCalledTimes(1);
154
- const saddBatchArgs = (mockRedis.saddBatch as any).mock.calls[0][0] as Array<{
155
- key: string;
156
- members: string[];
157
- }>;
158
- expect(saddBatchArgs).toEqual([
159
- {
160
- key: CacheKeys.roleApis("admin"),
161
- members: ["/api/login", "/api/user/del", "/api/user/list"]
162
- },
163
- { key: CacheKeys.roleApis("user"), members: ["/api/login"] }
164
- ]);
165
- });
166
-
167
- it("无权限时仍会清理旧缓存,但不写入成员", async () => {
168
- const roles = [{ id: 1, code: "empty", apis: [] }];
169
-
170
- mockDb.getAll = mock((opts: any) => {
171
- if (opts.table === "addon_admin_role") return Promise.resolve({ lists: roles, total: roles.length });
172
- return Promise.resolve({ lists: [], total: 0 });
173
- });
174
-
175
- await cacheHelper.rebuildRoleApiPermissions();
176
-
177
- expect(mockRedis.delBatch).toHaveBeenCalledTimes(1);
178
- expect(mockRedis.saddBatch).not.toHaveBeenCalled();
179
- });
180
- });
181
-
182
- describe("refreshRoleApiPermissions", () => {
183
- it("apiPaths 为空数组时只清理缓存,不查询 DB", async () => {
184
- mockDb.getAll = mock(() => Promise.resolve({ lists: [], total: 0 }));
185
-
186
- await cacheHelper.refreshRoleApiPermissions("admin", []);
187
-
188
- expect(mockRedis.del).toHaveBeenCalledWith(CacheKeys.roleApis("admin"));
189
- expect(mockDb.getAll).not.toHaveBeenCalled();
190
- });
191
-
192
- it("apiPaths 非空时直接覆盖更新该角色缓存(DEL + SADD)", async () => {
193
- await cacheHelper.refreshRoleApiPermissions("admin", ["/api/login", "/api/user/list"]);
194
-
195
- expect(mockRedis.del).toHaveBeenCalledWith(CacheKeys.roleApis("admin"));
196
- expect(mockRedis.sadd).toHaveBeenCalledWith(CacheKeys.roleApis("admin"), ["/api/login", "/api/user/list"]);
197
- });
198
- });
199
-
200
- describe("getApis", () => {
201
- it("返回缓存的接口列表", async () => {
202
- const apis = [{ id: 1, name: "登录" }];
203
- mockRedis.getObject = mock(() => Promise.resolve(apis));
204
-
205
- const result = await cacheHelper.getApis();
206
-
207
- expect(result).toEqual(apis);
208
- });
209
-
210
- it("缓存不存在时返回空数组", async () => {
211
- mockRedis.getObject = mock(() => Promise.resolve(null));
212
-
213
- const result = await cacheHelper.getApis();
214
-
215
- expect(result).toEqual([]);
216
- });
217
- });
218
-
219
- describe("getMenus", () => {
220
- it("返回缓存的菜单列表", async () => {
221
- const menus = [{ id: 1, name: "首页" }];
222
- mockRedis.getObject = mock(() => Promise.resolve(menus));
223
-
224
- const result = await cacheHelper.getMenus();
225
-
226
- expect(result).toEqual(menus);
227
- });
228
-
229
- it("缓存不存在时返回空数组", async () => {
230
- mockRedis.getObject = mock(() => Promise.resolve(null));
231
-
232
- const result = await cacheHelper.getMenus();
233
-
234
- expect(result).toEqual([]);
235
- });
236
- });
237
-
238
- describe("getRolePermissions", () => {
239
- it("返回角色的权限列表", async () => {
240
- const permissions = ["/api/login", "/api/user/list"];
241
- mockRedis.smembers = mock(() => Promise.resolve(permissions));
242
-
243
- const result = await cacheHelper.getRolePermissions("admin");
244
-
245
- expect(mockRedis.smembers).toHaveBeenCalledWith(CacheKeys.roleApis("admin"));
246
- expect(result).toEqual(permissions);
247
- });
248
-
249
- it("权限不存在时返回空数组", async () => {
250
- mockRedis.smembers = mock(() => Promise.resolve(null));
251
-
252
- const result = await cacheHelper.getRolePermissions("unknown");
253
-
254
- expect(result).toEqual([]);
255
- });
256
- });
257
-
258
- describe("checkRolePermission", () => {
259
- it("有权限时返回 true", async () => {
260
- mockRedis.sismember = mock(() => Promise.resolve(true));
261
-
262
- const result = await cacheHelper.checkRolePermission("admin", "/api/login");
263
-
264
- expect(mockRedis.sismember).toHaveBeenCalledWith(CacheKeys.roleApis("admin"), "/api/login");
265
- expect(result).toBe(true);
266
- });
267
-
268
- it("无权限时返回 false", async () => {
269
- mockRedis.sismember = mock(() => Promise.resolve(false));
270
-
271
- const result = await cacheHelper.checkRolePermission("user", "/api/admin/del");
272
-
273
- expect(result).toBe(false);
274
- });
275
- });
276
-
277
- describe("deleteRolePermissions", () => {
278
- it("删除成功返回 true", async () => {
279
- mockRedis.del = mock(() => Promise.resolve(1));
280
-
281
- const result = await cacheHelper.deleteRolePermissions("admin");
282
-
283
- expect(mockRedis.del).toHaveBeenCalledWith(CacheKeys.roleApis("admin"));
284
- expect(result).toBe(true);
285
- });
286
-
287
- it("不存在时返回 false", async () => {
288
- mockRedis.del = mock(() => Promise.resolve(0));
289
-
290
- const result = await cacheHelper.deleteRolePermissions("unknown");
291
-
292
- expect(result).toBe(false);
293
- });
294
- });
295
-
296
- describe("cacheAll", () => {
297
- it("按顺序调用三个缓存方法", async () => {
298
- const callOrder: string[] = [];
299
-
300
- // 使用 spy 记录调用顺序
301
- const originalCacheApis = cacheHelper.cacheApis.bind(cacheHelper);
302
- const originalCacheMenus = cacheHelper.cacheMenus.bind(cacheHelper);
303
- const originalRebuildRoleApiPermissions = cacheHelper.rebuildRoleApiPermissions.bind(cacheHelper);
304
-
305
- cacheHelper.cacheApis = async () => {
306
- callOrder.push("apis");
307
- await originalCacheApis();
308
- };
309
- cacheHelper.cacheMenus = async () => {
310
- callOrder.push("menus");
311
- await originalCacheMenus();
312
- };
313
- cacheHelper.rebuildRoleApiPermissions = async () => {
314
- callOrder.push("permissions");
315
- await originalRebuildRoleApiPermissions();
316
- };
317
-
318
- await cacheHelper.cacheAll();
319
-
320
- expect(callOrder).toEqual(["apis", "menus", "permissions"]);
321
- });
322
- });
323
- });
@@ -1,41 +0,0 @@
1
- /**
2
- * CacheKeys 测试
3
- */
4
-
5
- import { describe, expect, test } from "bun:test";
6
-
7
- import { CacheKeys } from "../lib/cacheKeys.js";
8
-
9
- describe("CacheKeys - Key 生成函数", () => {
10
- test("apisAll - 返回固定的接口缓存键", () => {
11
- expect(CacheKeys.apisAll()).toBe("befly:apis:all");
12
- });
13
-
14
- test("menusAll - 返回固定的菜单缓存键", () => {
15
- expect(CacheKeys.menusAll()).toBe("befly:menus:all");
16
- });
17
-
18
- test("roleInfo - 返回带角色代码的缓存键", () => {
19
- expect(CacheKeys.roleInfo("admin")).toBe("befly:role:info:admin");
20
- expect(CacheKeys.roleInfo("user")).toBe("befly:role:info:user");
21
- });
22
-
23
- test("roleApis - 返回带角色代码的权限缓存键", () => {
24
- expect(CacheKeys.roleApis("admin")).toBe("befly:role:apis:admin");
25
- expect(CacheKeys.roleApis("guest")).toBe("befly:role:apis:guest");
26
- });
27
-
28
- test("tableColumns - 返回带表名的结构缓存键", () => {
29
- expect(CacheKeys.tableColumns("user")).toBe("befly:table:columns:user");
30
- expect(CacheKeys.tableColumns("addon_admin_role")).toBe("befly:table:columns:addon_admin_role");
31
- });
32
-
33
- test("roleInfo - 特殊字符处理", () => {
34
- expect(CacheKeys.roleInfo("super-admin")).toBe("befly:role:info:super-admin");
35
- expect(CacheKeys.roleInfo("role_1")).toBe("befly:role:info:role_1");
36
- });
37
-
38
- test("tableColumns - 空字符串", () => {
39
- expect(CacheKeys.tableColumns("")).toBe("befly:table:columns:");
40
- });
41
- });
@@ -1,166 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import { checkApi } from "../checks/checkApi.js";
4
-
5
- describe("checkApi - routePath strict", () => {
6
- test("合法 routePath 应通过", async () => {
7
- let thrown: any = null;
8
-
9
- try {
10
- await checkApi([
11
- {
12
- name: "hello",
13
- handler: () => {
14
- return null;
15
- },
16
- routePath: "/api/hello",
17
- routePrefix: "/app"
18
- }
19
- ]);
20
- } catch (error: any) {
21
- thrown = error;
22
- }
23
-
24
- expect(thrown).toBeNull();
25
- });
26
-
27
- test("routePath 为空字符串应阻断启动", async () => {
28
- let thrown: any = null;
29
-
30
- try {
31
- await checkApi([
32
- {
33
- name: "hello",
34
- handler: () => null,
35
- routePath: "",
36
- routePrefix: "/app"
37
- }
38
- ]);
39
- } catch (error: any) {
40
- thrown = error;
41
- }
42
-
43
- expect(thrown).toBeTruthy();
44
- expect(thrown.message).toBe("接口结构检查失败");
45
- });
46
-
47
- test("routePath 非字符串应阻断启动", async () => {
48
- let thrown: any = null;
49
-
50
- try {
51
- await checkApi([
52
- {
53
- name: "hello",
54
- handler: () => null,
55
- routePath: 123,
56
- routePrefix: "/app"
57
- } as any
58
- ]);
59
- } catch (error: any) {
60
- thrown = error;
61
- }
62
-
63
- expect(thrown).toBeTruthy();
64
- expect(thrown.message).toBe("接口结构检查失败");
65
- });
66
-
67
- test("routePath 不允许 method 前缀(POST/api/...)", async () => {
68
- let thrown: any = null;
69
-
70
- try {
71
- await checkApi([
72
- {
73
- name: "hello",
74
- handler: () => null,
75
- routePath: "POST/api/hello",
76
- routePrefix: "/app"
77
- }
78
- ]);
79
- } catch (error: any) {
80
- thrown = error;
81
- }
82
-
83
- expect(thrown).toBeTruthy();
84
- expect(thrown.message).toBe("接口结构检查失败");
85
- });
86
-
87
- test("routePath 不允许 method + 空格(POST /api/...)", async () => {
88
- let thrown: any = null;
89
-
90
- try {
91
- await checkApi([
92
- {
93
- name: "hello",
94
- handler: () => null,
95
- routePath: "POST /api/hello",
96
- routePrefix: "/app"
97
- }
98
- ]);
99
- } catch (error: any) {
100
- thrown = error;
101
- }
102
-
103
- expect(thrown).toBeTruthy();
104
- expect(thrown.message).toBe("接口结构检查失败");
105
- });
106
-
107
- test("routePath 必须以 /api/ 开头", async () => {
108
- let thrown: any = null;
109
-
110
- try {
111
- await checkApi([
112
- {
113
- name: "hello",
114
- handler: () => null,
115
- routePath: "/app/hello",
116
- routePrefix: "/app"
117
- }
118
- ]);
119
- } catch (error: any) {
120
- thrown = error;
121
- }
122
-
123
- expect(thrown).toBeTruthy();
124
- expect(thrown.message).toBe("接口结构检查失败");
125
- });
126
-
127
- test("routePath 不允许包含空格", async () => {
128
- let thrown: any = null;
129
-
130
- try {
131
- await checkApi([
132
- {
133
- name: "hello",
134
- handler: () => null,
135
- routePath: "/api/hello world",
136
- routePrefix: "/app"
137
- }
138
- ]);
139
- } catch (error: any) {
140
- thrown = error;
141
- }
142
-
143
- expect(thrown).toBeTruthy();
144
- expect(thrown.message).toBe("接口结构检查失败");
145
- });
146
-
147
- test("routePath 不允许出现 /api//(重复斜杠)", async () => {
148
- let thrown: any = null;
149
-
150
- try {
151
- await checkApi([
152
- {
153
- name: "hello",
154
- handler: () => null,
155
- routePath: "/api//hello",
156
- routePrefix: "/app"
157
- }
158
- ]);
159
- } catch (error: any) {
160
- thrown = error;
161
- }
162
-
163
- expect(thrown).toBeTruthy();
164
- expect(thrown.message).toBe("接口结构检查失败");
165
- });
166
- });