befly 3.9.37 → 3.9.39
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 +38 -39
- package/befly.config.ts +62 -40
- package/checks/checkApi.ts +16 -16
- package/checks/checkApp.ts +19 -25
- package/checks/checkTable.ts +42 -42
- package/docs/README.md +42 -35
- package/docs/{api.md → api/api.md} +225 -235
- package/docs/cipher.md +71 -69
- package/docs/database.md +155 -153
- package/docs/{examples.md → guide/examples.md} +181 -181
- package/docs/guide/quickstart.md +331 -0
- package/docs/hooks/auth.md +38 -0
- package/docs/hooks/cors.md +28 -0
- package/docs/{hook.md → hooks/hook.md} +140 -57
- package/docs/hooks/parser.md +19 -0
- package/docs/hooks/rateLimit.md +47 -0
- package/docs/{redis.md → infra/redis.md} +84 -93
- package/docs/plugins/cipher.md +61 -0
- package/docs/plugins/database.md +128 -0
- package/docs/{plugin.md → plugins/plugin.md} +83 -81
- package/docs/quickstart.md +26 -26
- package/docs/{addon.md → reference/addon.md} +46 -46
- package/docs/{config.md → reference/config.md} +32 -80
- package/docs/{logger.md → reference/logger.md} +52 -52
- package/docs/{sync.md → reference/sync.md} +32 -35
- package/docs/{table.md → reference/table.md} +7 -7
- package/docs/{validator.md → reference/validator.md} +57 -57
- package/hooks/auth.ts +8 -4
- package/hooks/cors.ts +13 -13
- package/hooks/parser.ts +37 -17
- package/hooks/permission.ts +26 -14
- package/hooks/rateLimit.ts +276 -0
- package/hooks/validator.ts +15 -7
- package/lib/asyncContext.ts +43 -0
- package/lib/cacheHelper.ts +212 -81
- package/lib/cacheKeys.ts +38 -0
- package/lib/cipher.ts +30 -30
- package/lib/connect.ts +28 -28
- package/lib/dbHelper.ts +211 -109
- package/lib/jwt.ts +16 -16
- package/lib/logger.ts +610 -19
- package/lib/redisHelper.ts +185 -44
- package/lib/sqlBuilder.ts +90 -91
- package/lib/validator.ts +59 -39
- package/loader/loadApis.ts +53 -47
- package/loader/loadHooks.ts +40 -14
- package/loader/loadPlugins.ts +16 -17
- package/main.ts +57 -47
- package/package.json +47 -45
- package/paths.ts +15 -14
- package/plugins/cache.ts +5 -4
- package/plugins/cipher.ts +3 -3
- package/plugins/config.ts +2 -2
- package/plugins/db.ts +9 -9
- package/plugins/jwt.ts +3 -3
- package/plugins/logger.ts +8 -12
- package/plugins/redis.ts +8 -8
- package/plugins/tool.ts +6 -6
- package/router/api.ts +85 -56
- package/router/static.ts +12 -12
- package/sync/syncAll.ts +12 -12
- package/sync/syncApi.ts +55 -54
- package/sync/syncDb/apply.ts +20 -19
- package/sync/syncDb/constants.ts +25 -23
- package/sync/syncDb/ddl.ts +35 -36
- package/sync/syncDb/helpers.ts +6 -9
- package/sync/syncDb/schema.ts +10 -9
- package/sync/syncDb/sqlite.ts +7 -8
- package/sync/syncDb/table.ts +37 -35
- package/sync/syncDb/tableCreate.ts +21 -20
- package/sync/syncDb/types.ts +23 -20
- package/sync/syncDb/version.ts +10 -10
- package/sync/syncDb.ts +43 -36
- package/sync/syncDev.ts +74 -66
- package/sync/syncMenu.ts +190 -57
- package/tests/api-integration-array-number.test.ts +282 -0
- package/tests/befly-config-env.test.ts +78 -0
- package/tests/cacheHelper.test.ts +135 -104
- package/tests/cacheKeys.test.ts +41 -0
- package/tests/cipher.test.ts +90 -89
- package/tests/dbHelper-advanced.test.ts +140 -134
- package/tests/dbHelper-all-array-types.test.ts +316 -0
- package/tests/dbHelper-array-serialization.test.ts +258 -0
- package/tests/dbHelper-columns.test.ts +56 -55
- package/tests/dbHelper-execute.test.ts +45 -44
- package/tests/dbHelper-joins.test.ts +124 -119
- package/tests/fields-redis-cache.test.ts +29 -27
- package/tests/fields-validate.test.ts +38 -38
- package/tests/getClientIp.test.ts +54 -0
- package/tests/integration.test.ts +69 -67
- package/tests/jwt.test.ts +27 -26
- package/tests/logger.test.ts +267 -34
- package/tests/rateLimit-hook.test.ts +477 -0
- package/tests/redisHelper.test.ts +187 -188
- package/tests/redisKeys.test.ts +6 -73
- package/tests/scanConfig.test.ts +144 -0
- package/tests/sqlBuilder-advanced.test.ts +217 -215
- package/tests/sqlBuilder.test.ts +92 -91
- package/tests/sync-connection.test.ts +29 -29
- package/tests/syncDb-apply.test.ts +97 -96
- package/tests/syncDb-array-number.test.ts +160 -0
- package/tests/syncDb-constants.test.ts +48 -47
- package/tests/syncDb-ddl.test.ts +99 -98
- package/tests/syncDb-helpers.test.ts +29 -28
- package/tests/syncDb-schema.test.ts +61 -60
- package/tests/syncDb-types.test.ts +60 -59
- package/tests/syncMenu-paths.test.ts +68 -0
- package/tests/util.test.ts +42 -41
- package/tests/validator-array-number.test.ts +310 -0
- package/tests/validator-default.test.ts +373 -0
- package/tests/validator.test.ts +271 -266
- package/tsconfig.json +4 -5
- package/types/api.d.ts +7 -12
- package/types/befly.d.ts +60 -13
- package/types/cache.d.ts +8 -4
- package/types/common.d.ts +17 -9
- package/types/context.d.ts +2 -2
- package/types/crypto.d.ts +23 -0
- package/types/database.d.ts +19 -19
- package/types/hook.d.ts +2 -2
- package/types/jwt.d.ts +118 -0
- package/types/logger.d.ts +30 -0
- package/types/plugin.d.ts +4 -4
- package/types/redis.d.ts +7 -3
- package/types/roleApisCache.ts +23 -0
- package/types/sync.d.ts +10 -10
- package/types/table.d.ts +50 -9
- package/types/validate.d.ts +69 -0
- package/utils/addonHelper.ts +90 -0
- package/utils/arrayKeysToCamel.ts +18 -0
- package/utils/calcPerfTime.ts +13 -0
- package/utils/configTypes.ts +3 -0
- package/utils/cors.ts +19 -0
- package/utils/fieldClear.ts +75 -0
- package/utils/genShortId.ts +12 -0
- package/utils/getClientIp.ts +45 -0
- package/utils/keysToCamel.ts +22 -0
- package/utils/keysToSnake.ts +22 -0
- package/utils/modules.ts +98 -0
- package/utils/pickFields.ts +19 -0
- package/utils/process.ts +56 -0
- package/utils/regex.ts +225 -0
- package/utils/response.ts +115 -0
- package/utils/route.ts +23 -0
- package/utils/scanConfig.ts +142 -0
- package/utils/scanFiles.ts +48 -0
- package/.prettierignore +0 -2
- package/.prettierrc +0 -12
- package/docs/1-/345/237/272/346/234/254/344/273/213/347/273/215.md +0 -35
- package/docs/2-/345/210/235/346/255/245/344/275/223/351/252/214.md +0 -64
- package/docs/3-/347/254/254/344/270/200/344/270/252/346/216/245/345/217/243.md +0 -46
- package/docs/4-/346/223/215/344/275/234/346/225/260/346/215/256/345/272/223.md +0 -172
- package/hooks/requestLogger.ts +0 -84
- package/types/index.ts +0 -24
- package/util.ts +0 -283
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CacheHelper 单元测试
|
|
3
3
|
*/
|
|
4
|
-
import { describe, it, expect, beforeEach, afterEach, mock } from 'bun:test';
|
|
5
|
-
import { CacheHelper } from '../lib/cacheHelper.js';
|
|
6
|
-
import { Logger, setMockLogger } from '../lib/logger.js';
|
|
7
|
-
import { RedisKeys } from 'befly-shared/redisKeys';
|
|
8
4
|
|
|
9
|
-
import type { BeflyContext } from
|
|
5
|
+
import type { BeflyContext } from "../types/befly.js";
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test";
|
|
8
|
+
|
|
9
|
+
import { CacheHelper } from "../lib/cacheHelper.js";
|
|
10
|
+
import { CacheKeys } from "../lib/cacheKeys.js";
|
|
11
|
+
import { setMockLogger } from "../lib/logger.js";
|
|
10
12
|
|
|
11
13
|
// Mock pino logger
|
|
12
14
|
const mockPino = {
|
|
@@ -18,10 +20,10 @@ const mockPino = {
|
|
|
18
20
|
trace: mock(() => {}),
|
|
19
21
|
silent: mock(() => {}),
|
|
20
22
|
child: mock(() => mockPino),
|
|
21
|
-
level:
|
|
23
|
+
level: "info"
|
|
22
24
|
};
|
|
23
25
|
|
|
24
|
-
describe(
|
|
26
|
+
describe("CacheHelper", () => {
|
|
25
27
|
let cacheHelper: CacheHelper;
|
|
26
28
|
let mockBefly: BeflyContext;
|
|
27
29
|
let mockDb: any;
|
|
@@ -34,17 +36,20 @@ describe('CacheHelper', () => {
|
|
|
34
36
|
// Mock 数据库方法
|
|
35
37
|
mockDb = {
|
|
36
38
|
tableExists: mock(() => Promise.resolve(true)),
|
|
37
|
-
getAll: mock(() => Promise.resolve([]))
|
|
39
|
+
getAll: mock(() => Promise.resolve({ lists: [], total: 0 }))
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
// Mock Redis 方法
|
|
41
43
|
mockRedis = {
|
|
42
|
-
setObject: mock(() => Promise.resolve(
|
|
44
|
+
setObject: mock(() => Promise.resolve("OK")),
|
|
43
45
|
getObject: mock(() => Promise.resolve(null)),
|
|
44
46
|
sadd: mock(() => Promise.resolve(1)),
|
|
47
|
+
saddBatch: mock(() => Promise.resolve(1)),
|
|
45
48
|
smembers: mock(() => Promise.resolve([])),
|
|
46
49
|
sismember: mock(() => Promise.resolve(0)),
|
|
47
|
-
del: mock(() => Promise.resolve(1))
|
|
50
|
+
del: mock(() => Promise.resolve(1)),
|
|
51
|
+
delBatch: mock(() => Promise.resolve(1)),
|
|
52
|
+
exists: mock(() => Promise.resolve(true))
|
|
48
53
|
};
|
|
49
54
|
|
|
50
55
|
// 创建 mock befly context
|
|
@@ -61,29 +66,29 @@ describe('CacheHelper', () => {
|
|
|
61
66
|
setMockLogger(null);
|
|
62
67
|
});
|
|
63
68
|
|
|
64
|
-
describe(
|
|
65
|
-
it(
|
|
69
|
+
describe("cacheApis", () => {
|
|
70
|
+
it("表不存在时跳过缓存", async () => {
|
|
66
71
|
mockDb.tableExists = mock(() => Promise.resolve(false));
|
|
67
72
|
|
|
68
73
|
await cacheHelper.cacheApis();
|
|
69
74
|
|
|
70
|
-
expect(mockDb.tableExists).toHaveBeenCalledWith(
|
|
75
|
+
expect(mockDb.tableExists).toHaveBeenCalledWith("addon_admin_api");
|
|
71
76
|
expect(mockDb.getAll).not.toHaveBeenCalled();
|
|
72
77
|
});
|
|
73
78
|
|
|
74
|
-
it(
|
|
79
|
+
it("正常缓存接口列表", async () => {
|
|
75
80
|
const apis = [
|
|
76
|
-
{ id: 1, name:
|
|
77
|
-
{ id: 2, name:
|
|
81
|
+
{ id: 1, name: "登录", path: "/api/login", method: "POST" },
|
|
82
|
+
{ id: 2, name: "用户列表", path: "/api/user/list", method: "GET" }
|
|
78
83
|
];
|
|
79
84
|
mockDb.getAll = mock(() => Promise.resolve({ lists: apis, total: apis.length }));
|
|
80
85
|
|
|
81
86
|
await cacheHelper.cacheApis();
|
|
82
87
|
|
|
83
|
-
expect(mockRedis.setObject).toHaveBeenCalledWith(
|
|
88
|
+
expect(mockRedis.setObject).toHaveBeenCalledWith(CacheKeys.apisAll(), apis);
|
|
84
89
|
});
|
|
85
90
|
|
|
86
|
-
it(
|
|
91
|
+
it("缓存失败时记录警告", async () => {
|
|
87
92
|
mockRedis.setObject = mock(() => Promise.resolve(null));
|
|
88
93
|
|
|
89
94
|
await cacheHelper.cacheApis();
|
|
@@ -91,8 +96,8 @@ describe('CacheHelper', () => {
|
|
|
91
96
|
expect(mockPino.warn).toHaveBeenCalled();
|
|
92
97
|
});
|
|
93
98
|
|
|
94
|
-
it(
|
|
95
|
-
mockDb.getAll = mock(() => Promise.reject(new Error(
|
|
99
|
+
it("异常时记录错误", async () => {
|
|
100
|
+
mockDb.getAll = mock(() => Promise.reject(new Error("DB Error")));
|
|
96
101
|
|
|
97
102
|
await cacheHelper.cacheApis();
|
|
98
103
|
|
|
@@ -100,110 +105,136 @@ describe('CacheHelper', () => {
|
|
|
100
105
|
});
|
|
101
106
|
});
|
|
102
107
|
|
|
103
|
-
describe(
|
|
104
|
-
it(
|
|
108
|
+
describe("cacheMenus", () => {
|
|
109
|
+
it("表不存在时跳过缓存", async () => {
|
|
105
110
|
mockDb.tableExists = mock(() => Promise.resolve(false));
|
|
106
111
|
|
|
107
112
|
await cacheHelper.cacheMenus();
|
|
108
113
|
|
|
109
|
-
expect(mockDb.tableExists).toHaveBeenCalledWith(
|
|
114
|
+
expect(mockDb.tableExists).toHaveBeenCalledWith("addon_admin_menu");
|
|
110
115
|
expect(mockDb.getAll).not.toHaveBeenCalled();
|
|
111
116
|
});
|
|
112
117
|
|
|
113
|
-
it(
|
|
118
|
+
it("正常缓存菜单列表", async () => {
|
|
114
119
|
const menus = [
|
|
115
|
-
{ id: 1, pid: 0, name:
|
|
116
|
-
{ id: 2, pid: 0, name:
|
|
120
|
+
{ id: 1, pid: 0, name: "首页", path: "/home", sort: 1 },
|
|
121
|
+
{ id: 2, pid: 0, name: "用户管理", path: "/user", sort: 2 }
|
|
117
122
|
];
|
|
118
123
|
mockDb.getAll = mock(() => Promise.resolve({ lists: menus, total: menus.length }));
|
|
119
124
|
|
|
120
125
|
await cacheHelper.cacheMenus();
|
|
121
126
|
|
|
122
|
-
expect(mockRedis.setObject).toHaveBeenCalledWith(
|
|
127
|
+
expect(mockRedis.setObject).toHaveBeenCalledWith(CacheKeys.menusAll(), menus);
|
|
123
128
|
});
|
|
124
129
|
});
|
|
125
130
|
|
|
126
|
-
describe(
|
|
127
|
-
it(
|
|
131
|
+
describe("rebuildRoleApiPermissions", () => {
|
|
132
|
+
it("表不存在时跳过缓存", async () => {
|
|
128
133
|
mockDb.tableExists = mock((table: string) => {
|
|
129
|
-
if (table ===
|
|
130
|
-
if (table ===
|
|
134
|
+
if (table === "addon_admin_api") return Promise.resolve(true);
|
|
135
|
+
if (table === "addon_admin_role") return Promise.resolve(false);
|
|
131
136
|
return Promise.resolve(false);
|
|
132
137
|
});
|
|
133
138
|
|
|
134
|
-
await cacheHelper.
|
|
139
|
+
await cacheHelper.rebuildRoleApiPermissions();
|
|
135
140
|
|
|
136
141
|
expect(mockPino.warn).toHaveBeenCalled();
|
|
137
142
|
});
|
|
138
143
|
|
|
139
|
-
it(
|
|
144
|
+
it("正常重建角色权限(覆盖更新)", async () => {
|
|
140
145
|
const roles = [
|
|
141
|
-
{ id: 1, code:
|
|
142
|
-
{ id: 2, code:
|
|
146
|
+
{ id: 1, code: "admin", apis: [1, "2", 3] },
|
|
147
|
+
{ id: 2, code: "user", apis: [1] }
|
|
143
148
|
];
|
|
144
149
|
const apis = [
|
|
145
|
-
{ id: 1, path:
|
|
146
|
-
{ id: 2, path:
|
|
147
|
-
{ id: 3, path:
|
|
150
|
+
{ id: 1, path: "/api/login", method: "POST" },
|
|
151
|
+
{ id: 2, path: "/api/user/list", method: "GET" },
|
|
152
|
+
{ id: 3, path: "/api/user/del", method: "POST" }
|
|
148
153
|
];
|
|
149
154
|
|
|
150
155
|
mockDb.getAll = mock((opts: any) => {
|
|
151
|
-
if (opts.table ===
|
|
152
|
-
if (opts.table ===
|
|
156
|
+
if (opts.table === "addon_admin_role") return Promise.resolve({ lists: roles, total: roles.length });
|
|
157
|
+
if (opts.table === "addon_admin_api") {
|
|
158
|
+
// rebuildRoleApiPermissions 会通过 where: { id$in: [...] } 分块查询
|
|
159
|
+
return Promise.resolve({ lists: apis, total: apis.length });
|
|
160
|
+
}
|
|
153
161
|
return Promise.resolve({ lists: [], total: 0 });
|
|
154
162
|
});
|
|
155
163
|
|
|
156
|
-
await cacheHelper.
|
|
157
|
-
|
|
158
|
-
//
|
|
159
|
-
expect(mockRedis.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
expect(mockRedis.
|
|
164
|
+
await cacheHelper.rebuildRoleApiPermissions();
|
|
165
|
+
|
|
166
|
+
// 验证批量删除角色 key
|
|
167
|
+
expect(mockRedis.delBatch).toHaveBeenCalledTimes(1);
|
|
168
|
+
const delBatchArgs = (mockRedis.delBatch as any).mock.calls[0][0] as string[];
|
|
169
|
+
expect(delBatchArgs).toEqual([CacheKeys.roleApis("admin"), CacheKeys.roleApis("user")]);
|
|
170
|
+
|
|
171
|
+
// 验证批量写入
|
|
172
|
+
expect(mockRedis.saddBatch).toHaveBeenCalledTimes(1);
|
|
173
|
+
const saddBatchArgs = (mockRedis.saddBatch as any).mock.calls[0][0] as Array<{
|
|
174
|
+
key: string;
|
|
175
|
+
members: string[];
|
|
176
|
+
}>;
|
|
177
|
+
expect(saddBatchArgs).toEqual([
|
|
178
|
+
{
|
|
179
|
+
key: CacheKeys.roleApis("admin"),
|
|
180
|
+
members: ["GET/api/user/list", "POST/api/login", "POST/api/user/del"]
|
|
181
|
+
},
|
|
182
|
+
{ key: CacheKeys.roleApis("user"), members: ["POST/api/login"] }
|
|
183
|
+
]);
|
|
165
184
|
});
|
|
166
185
|
|
|
167
|
-
it(
|
|
168
|
-
const roles = [{ id: 1, code:
|
|
169
|
-
const apis = [{ id: 1, path:
|
|
186
|
+
it("无权限时仍会清理旧缓存,但不写入成员", async () => {
|
|
187
|
+
const roles = [{ id: 1, code: "empty", apis: [] }];
|
|
188
|
+
const apis = [{ id: 1, path: "/api/login", method: "POST" }];
|
|
170
189
|
|
|
171
190
|
mockDb.getAll = mock((opts: any) => {
|
|
172
|
-
if (opts.table ===
|
|
173
|
-
if (opts.table ===
|
|
191
|
+
if (opts.table === "addon_admin_role") return Promise.resolve({ lists: roles, total: roles.length });
|
|
192
|
+
if (opts.table === "addon_admin_api") return Promise.resolve({ lists: apis, total: apis.length });
|
|
174
193
|
return Promise.resolve({ lists: [], total: 0 });
|
|
175
194
|
});
|
|
176
195
|
|
|
177
|
-
await cacheHelper.
|
|
196
|
+
await cacheHelper.rebuildRoleApiPermissions();
|
|
178
197
|
|
|
179
|
-
expect(mockRedis.
|
|
198
|
+
expect(mockRedis.delBatch).toHaveBeenCalledTimes(1);
|
|
199
|
+
expect(mockRedis.saddBatch).not.toHaveBeenCalled();
|
|
180
200
|
});
|
|
201
|
+
});
|
|
181
202
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
203
|
+
describe("refreshRoleApiPermissions", () => {
|
|
204
|
+
it("apiIds 为空数组时只清理缓存,不查询 API 表", async () => {
|
|
205
|
+
mockDb.getAll = mock(() => Promise.resolve({ lists: [], total: 0 }));
|
|
206
|
+
|
|
207
|
+
await cacheHelper.refreshRoleApiPermissions("admin", []);
|
|
208
|
+
|
|
209
|
+
expect(mockRedis.del).toHaveBeenCalledWith(CacheKeys.roleApis("admin"));
|
|
210
|
+
expect(mockDb.getAll).not.toHaveBeenCalledWith(
|
|
211
|
+
expect.objectContaining({
|
|
212
|
+
table: "addon_admin_api"
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("apiIds 非空时使用 $in 查询并重建该角色缓存(覆盖更新)", async () => {
|
|
218
|
+
const apis = [
|
|
219
|
+
{ id: 1, path: "/api/login", method: "POST" },
|
|
220
|
+
{ id: 2, path: "/api/user/list", method: "GET" }
|
|
187
221
|
];
|
|
188
|
-
const apis = [{ id: 1, path: '/api/test', method: 'GET' }];
|
|
189
222
|
|
|
190
223
|
mockDb.getAll = mock((opts: any) => {
|
|
191
|
-
if (opts.table ===
|
|
192
|
-
if (opts.table === 'addon_admin_api') return Promise.resolve({ lists: apis, total: apis.length });
|
|
224
|
+
if (opts.table === "addon_admin_api") return Promise.resolve({ lists: apis, total: apis.length });
|
|
193
225
|
return Promise.resolve({ lists: [], total: 0 });
|
|
194
226
|
});
|
|
195
227
|
|
|
196
|
-
await cacheHelper.
|
|
228
|
+
await cacheHelper.refreshRoleApiPermissions("admin", [1, 2]);
|
|
197
229
|
|
|
198
|
-
|
|
199
|
-
expect(mockRedis.
|
|
200
|
-
expect(mockRedis.sadd).toHaveBeenCalledTimes(3);
|
|
230
|
+
expect(mockRedis.del).toHaveBeenCalledWith(CacheKeys.roleApis("admin"));
|
|
231
|
+
expect(mockRedis.sadd).toHaveBeenCalledWith(CacheKeys.roleApis("admin"), ["POST/api/login", "GET/api/user/list"]);
|
|
201
232
|
});
|
|
202
233
|
});
|
|
203
234
|
|
|
204
|
-
describe(
|
|
205
|
-
it(
|
|
206
|
-
const apis = [{ id: 1, name:
|
|
235
|
+
describe("getApis", () => {
|
|
236
|
+
it("返回缓存的接口列表", async () => {
|
|
237
|
+
const apis = [{ id: 1, name: "登录" }];
|
|
207
238
|
mockRedis.getObject = mock(() => Promise.resolve(apis));
|
|
208
239
|
|
|
209
240
|
const result = await cacheHelper.getApis();
|
|
@@ -211,7 +242,7 @@ describe('CacheHelper', () => {
|
|
|
211
242
|
expect(result).toEqual(apis);
|
|
212
243
|
});
|
|
213
244
|
|
|
214
|
-
it(
|
|
245
|
+
it("缓存不存在时返回空数组", async () => {
|
|
215
246
|
mockRedis.getObject = mock(() => Promise.resolve(null));
|
|
216
247
|
|
|
217
248
|
const result = await cacheHelper.getApis();
|
|
@@ -220,9 +251,9 @@ describe('CacheHelper', () => {
|
|
|
220
251
|
});
|
|
221
252
|
});
|
|
222
253
|
|
|
223
|
-
describe(
|
|
224
|
-
it(
|
|
225
|
-
const menus = [{ id: 1, name:
|
|
254
|
+
describe("getMenus", () => {
|
|
255
|
+
it("返回缓存的菜单列表", async () => {
|
|
256
|
+
const menus = [{ id: 1, name: "首页" }];
|
|
226
257
|
mockRedis.getObject = mock(() => Promise.resolve(menus));
|
|
227
258
|
|
|
228
259
|
const result = await cacheHelper.getMenus();
|
|
@@ -230,7 +261,7 @@ describe('CacheHelper', () => {
|
|
|
230
261
|
expect(result).toEqual(menus);
|
|
231
262
|
});
|
|
232
263
|
|
|
233
|
-
it(
|
|
264
|
+
it("缓存不存在时返回空数组", async () => {
|
|
234
265
|
mockRedis.getObject = mock(() => Promise.resolve(null));
|
|
235
266
|
|
|
236
267
|
const result = await cacheHelper.getMenus();
|
|
@@ -239,89 +270,89 @@ describe('CacheHelper', () => {
|
|
|
239
270
|
});
|
|
240
271
|
});
|
|
241
272
|
|
|
242
|
-
describe(
|
|
243
|
-
it(
|
|
244
|
-
const permissions = [
|
|
273
|
+
describe("getRolePermissions", () => {
|
|
274
|
+
it("返回角色的权限列表", async () => {
|
|
275
|
+
const permissions = ["POST/api/login", "GET/api/user/list"];
|
|
245
276
|
mockRedis.smembers = mock(() => Promise.resolve(permissions));
|
|
246
277
|
|
|
247
|
-
const result = await cacheHelper.getRolePermissions(
|
|
278
|
+
const result = await cacheHelper.getRolePermissions("admin");
|
|
248
279
|
|
|
249
|
-
expect(mockRedis.smembers).toHaveBeenCalledWith(
|
|
280
|
+
expect(mockRedis.smembers).toHaveBeenCalledWith(CacheKeys.roleApis("admin"));
|
|
250
281
|
expect(result).toEqual(permissions);
|
|
251
282
|
});
|
|
252
283
|
|
|
253
|
-
it(
|
|
284
|
+
it("权限不存在时返回空数组", async () => {
|
|
254
285
|
mockRedis.smembers = mock(() => Promise.resolve(null));
|
|
255
286
|
|
|
256
|
-
const result = await cacheHelper.getRolePermissions(
|
|
287
|
+
const result = await cacheHelper.getRolePermissions("unknown");
|
|
257
288
|
|
|
258
289
|
expect(result).toEqual([]);
|
|
259
290
|
});
|
|
260
291
|
});
|
|
261
292
|
|
|
262
|
-
describe(
|
|
263
|
-
it(
|
|
293
|
+
describe("checkRolePermission", () => {
|
|
294
|
+
it("有权限时返回 true", async () => {
|
|
264
295
|
mockRedis.sismember = mock(() => Promise.resolve(true));
|
|
265
296
|
|
|
266
|
-
const result = await cacheHelper.checkRolePermission(
|
|
297
|
+
const result = await cacheHelper.checkRolePermission("admin", "POST/api/login");
|
|
267
298
|
|
|
268
|
-
expect(mockRedis.sismember).toHaveBeenCalledWith(
|
|
299
|
+
expect(mockRedis.sismember).toHaveBeenCalledWith(CacheKeys.roleApis("admin"), "POST/api/login");
|
|
269
300
|
expect(result).toBe(true);
|
|
270
301
|
});
|
|
271
302
|
|
|
272
|
-
it(
|
|
303
|
+
it("无权限时返回 false", async () => {
|
|
273
304
|
mockRedis.sismember = mock(() => Promise.resolve(false));
|
|
274
305
|
|
|
275
|
-
const result = await cacheHelper.checkRolePermission(
|
|
306
|
+
const result = await cacheHelper.checkRolePermission("user", "POST/api/admin/del");
|
|
276
307
|
|
|
277
308
|
expect(result).toBe(false);
|
|
278
309
|
});
|
|
279
310
|
});
|
|
280
311
|
|
|
281
|
-
describe(
|
|
282
|
-
it(
|
|
312
|
+
describe("deleteRolePermissions", () => {
|
|
313
|
+
it("删除成功返回 true", async () => {
|
|
283
314
|
mockRedis.del = mock(() => Promise.resolve(1));
|
|
284
315
|
|
|
285
|
-
const result = await cacheHelper.deleteRolePermissions(
|
|
316
|
+
const result = await cacheHelper.deleteRolePermissions("admin");
|
|
286
317
|
|
|
287
|
-
expect(mockRedis.del).toHaveBeenCalledWith(
|
|
318
|
+
expect(mockRedis.del).toHaveBeenCalledWith(CacheKeys.roleApis("admin"));
|
|
288
319
|
expect(result).toBe(true);
|
|
289
320
|
});
|
|
290
321
|
|
|
291
|
-
it(
|
|
322
|
+
it("不存在时返回 false", async () => {
|
|
292
323
|
mockRedis.del = mock(() => Promise.resolve(0));
|
|
293
324
|
|
|
294
|
-
const result = await cacheHelper.deleteRolePermissions(
|
|
325
|
+
const result = await cacheHelper.deleteRolePermissions("unknown");
|
|
295
326
|
|
|
296
327
|
expect(result).toBe(false);
|
|
297
328
|
});
|
|
298
329
|
});
|
|
299
330
|
|
|
300
|
-
describe(
|
|
301
|
-
it(
|
|
331
|
+
describe("cacheAll", () => {
|
|
332
|
+
it("按顺序调用三个缓存方法", async () => {
|
|
302
333
|
const callOrder: string[] = [];
|
|
303
334
|
|
|
304
335
|
// 使用 spy 记录调用顺序
|
|
305
336
|
const originalCacheApis = cacheHelper.cacheApis.bind(cacheHelper);
|
|
306
337
|
const originalCacheMenus = cacheHelper.cacheMenus.bind(cacheHelper);
|
|
307
|
-
const
|
|
338
|
+
const originalRebuildRoleApiPermissions = cacheHelper.rebuildRoleApiPermissions.bind(cacheHelper);
|
|
308
339
|
|
|
309
340
|
cacheHelper.cacheApis = async () => {
|
|
310
|
-
callOrder.push(
|
|
341
|
+
callOrder.push("apis");
|
|
311
342
|
await originalCacheApis();
|
|
312
343
|
};
|
|
313
344
|
cacheHelper.cacheMenus = async () => {
|
|
314
|
-
callOrder.push(
|
|
345
|
+
callOrder.push("menus");
|
|
315
346
|
await originalCacheMenus();
|
|
316
347
|
};
|
|
317
|
-
cacheHelper.
|
|
318
|
-
callOrder.push(
|
|
319
|
-
await
|
|
348
|
+
cacheHelper.rebuildRoleApiPermissions = async () => {
|
|
349
|
+
callOrder.push("permissions");
|
|
350
|
+
await originalRebuildRoleApiPermissions();
|
|
320
351
|
};
|
|
321
352
|
|
|
322
353
|
await cacheHelper.cacheAll();
|
|
323
354
|
|
|
324
|
-
expect(callOrder).toEqual([
|
|
355
|
+
expect(callOrder).toEqual(["apis", "menus", "permissions"]);
|
|
325
356
|
});
|
|
326
357
|
});
|
|
327
358
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
});
|