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
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 验证所有数组类型的序列化/反序列化支持
|
|
3
|
+
*
|
|
4
|
+
* 测试类型:
|
|
5
|
+
* - array_string
|
|
6
|
+
* - array_text
|
|
7
|
+
* - array_number_string
|
|
8
|
+
* - array_number_text
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, expect, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
describe("所有数组类型的序列化/反序列化支持", () => {
|
|
14
|
+
// 模拟 serializeArrayFields 方法
|
|
15
|
+
const serializeArrayFields = (data: Record<string, any>): Record<string, any> => {
|
|
16
|
+
const serialized = { ...data };
|
|
17
|
+
|
|
18
|
+
for (const [key, value] of Object.entries(serialized)) {
|
|
19
|
+
if (value === null || value === undefined) continue;
|
|
20
|
+
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
serialized[key] = JSON.stringify(value);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return serialized;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// 模拟 deserializeArrayFields 方法
|
|
30
|
+
const deserializeArrayFields = <T = any>(data: Record<string, any> | null): T | null => {
|
|
31
|
+
if (!data) return null;
|
|
32
|
+
|
|
33
|
+
const deserialized = { ...data };
|
|
34
|
+
|
|
35
|
+
for (const [key, value] of Object.entries(deserialized)) {
|
|
36
|
+
if (typeof value !== "string") continue;
|
|
37
|
+
|
|
38
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(value);
|
|
41
|
+
if (Array.isArray(parsed)) {
|
|
42
|
+
deserialized[key] = parsed;
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
// 解析失败则保持原值
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return deserialized as T;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// ==================== array_string 类型 ====================
|
|
54
|
+
|
|
55
|
+
test("array_string: 字符串数组序列化和反序列化", () => {
|
|
56
|
+
const data = {
|
|
57
|
+
tags: ["tag1", "tag2", "tag3"]
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const serialized = serializeArrayFields(data);
|
|
61
|
+
expect(serialized.tags).toBe('["tag1","tag2","tag3"]');
|
|
62
|
+
|
|
63
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
64
|
+
expect(deserialized.tags).toEqual(["tag1", "tag2", "tag3"]);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("array_string: 空数组", () => {
|
|
68
|
+
const data = {
|
|
69
|
+
tags: []
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const serialized = serializeArrayFields(data);
|
|
73
|
+
expect(serialized.tags).toBe("[]");
|
|
74
|
+
|
|
75
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
76
|
+
expect(deserialized.tags).toEqual([]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ==================== array_text 类型 ====================
|
|
80
|
+
|
|
81
|
+
test("array_text: 长文本数组序列化和反序列化", () => {
|
|
82
|
+
const data = {
|
|
83
|
+
descriptions: ["这是一段很长的文本描述" + "x".repeat(500), "另一段很长的描述" + "y".repeat(500)]
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const serialized = serializeArrayFields(data);
|
|
87
|
+
expect(typeof serialized.descriptions).toBe("string");
|
|
88
|
+
expect(serialized.descriptions.startsWith("[")).toBe(true);
|
|
89
|
+
|
|
90
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
91
|
+
expect(deserialized.descriptions).toEqual(data.descriptions);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("array_text: 包含特殊字符的文本数组", () => {
|
|
95
|
+
const data = {
|
|
96
|
+
contents: ['Text with "quotes"', "Text with 'apostrophes'", "Text with\nnewlines", "Text with\ttabs"]
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const serialized = serializeArrayFields(data);
|
|
100
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
101
|
+
expect(deserialized.contents).toEqual(data.contents);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// ==================== array_number_string 类型 ====================
|
|
105
|
+
|
|
106
|
+
test("array_number_string: 数字数组序列化和反序列化", () => {
|
|
107
|
+
const data = {
|
|
108
|
+
menuIds: [1, 2, 3, 4, 5],
|
|
109
|
+
apiIds: [10, 20, 30, 40, 50]
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const serialized = serializeArrayFields(data);
|
|
113
|
+
expect(serialized.menuIds).toBe("[1,2,3,4,5]");
|
|
114
|
+
expect(serialized.apiIds).toBe("[10,20,30,40,50]");
|
|
115
|
+
|
|
116
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
117
|
+
expect(deserialized.menuIds).toEqual([1, 2, 3, 4, 5]);
|
|
118
|
+
expect(deserialized.apiIds).toEqual([10, 20, 30, 40, 50]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("array_number_string: 大数字数组", () => {
|
|
122
|
+
const data = {
|
|
123
|
+
ids: [999999999999, 888888888888, 777777777777]
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const serialized = serializeArrayFields(data);
|
|
127
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
128
|
+
expect(deserialized.ids).toEqual(data.ids);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("array_number_string: 负数和小数", () => {
|
|
132
|
+
const data = {
|
|
133
|
+
numbers: [-1, -2.5, 0, 3.14, 100]
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const serialized = serializeArrayFields(data);
|
|
137
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
138
|
+
expect(deserialized.numbers).toEqual(data.numbers);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ==================== array_number_text 类型 ====================
|
|
142
|
+
|
|
143
|
+
test("array_number_text: 长数字数组序列化和反序列化", () => {
|
|
144
|
+
const data = {
|
|
145
|
+
historyIds: Array.from({ length: 500 }, (_, i) => i + 1)
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const serialized = serializeArrayFields(data);
|
|
149
|
+
expect(typeof serialized.historyIds).toBe("string");
|
|
150
|
+
expect(serialized.historyIds.length).toBeGreaterThan(1000);
|
|
151
|
+
|
|
152
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
153
|
+
expect(deserialized.historyIds).toEqual(data.historyIds);
|
|
154
|
+
expect(deserialized.historyIds.length).toBe(500);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("array_number_text: 时间戳数组", () => {
|
|
158
|
+
const data = {
|
|
159
|
+
timestamps: [1702540800000, 1702627200000, 1702713600000]
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const serialized = serializeArrayFields(data);
|
|
163
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
164
|
+
expect(deserialized.timestamps).toEqual(data.timestamps);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// ==================== 混合场景测试 ====================
|
|
168
|
+
|
|
169
|
+
test("混合场景: 所有数组类型同时存在", () => {
|
|
170
|
+
const data = {
|
|
171
|
+
// array_string
|
|
172
|
+
tags: ["tag1", "tag2"],
|
|
173
|
+
// array_text
|
|
174
|
+
descriptions: ["Long text 1", "Long text 2"],
|
|
175
|
+
// array_number_string
|
|
176
|
+
menuIds: [1, 2, 3],
|
|
177
|
+
// array_number_text
|
|
178
|
+
historyIds: Array.from({ length: 100 }, (_, i) => i),
|
|
179
|
+
// 非数组字段
|
|
180
|
+
name: "test",
|
|
181
|
+
count: 10
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const serialized = serializeArrayFields(data);
|
|
185
|
+
|
|
186
|
+
// 验证序列化
|
|
187
|
+
expect(serialized.tags).toBe('["tag1","tag2"]');
|
|
188
|
+
expect(serialized.descriptions).toBe('["Long text 1","Long text 2"]');
|
|
189
|
+
expect(serialized.menuIds).toBe("[1,2,3]");
|
|
190
|
+
expect(typeof serialized.historyIds).toBe("string");
|
|
191
|
+
expect(serialized.name).toBe("test");
|
|
192
|
+
expect(serialized.count).toBe(10);
|
|
193
|
+
|
|
194
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
195
|
+
|
|
196
|
+
// 验证反序列化
|
|
197
|
+
expect(deserialized.tags).toEqual(["tag1", "tag2"]);
|
|
198
|
+
expect(deserialized.descriptions).toEqual(["Long text 1", "Long text 2"]);
|
|
199
|
+
expect(deserialized.menuIds).toEqual([1, 2, 3]);
|
|
200
|
+
expect(deserialized.historyIds).toEqual(data.historyIds);
|
|
201
|
+
expect(deserialized.name).toBe("test");
|
|
202
|
+
expect(deserialized.count).toBe(10);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("混合场景: 包含 null 和 undefined 的数组字段", () => {
|
|
206
|
+
const data = {
|
|
207
|
+
tags: ["tag1", "tag2"],
|
|
208
|
+
menuIds: null,
|
|
209
|
+
apiIds: undefined,
|
|
210
|
+
name: "test"
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const serialized = serializeArrayFields(data);
|
|
214
|
+
expect(serialized.tags).toBe('["tag1","tag2"]');
|
|
215
|
+
expect(serialized.menuIds).toBeNull();
|
|
216
|
+
expect(serialized.apiIds).toBeUndefined();
|
|
217
|
+
|
|
218
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
219
|
+
expect(deserialized.tags).toEqual(["tag1", "tag2"]);
|
|
220
|
+
expect(deserialized.menuIds).toBeNull();
|
|
221
|
+
expect(deserialized.apiIds).toBeUndefined();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// ==================== 数据类型覆盖测试 ====================
|
|
225
|
+
|
|
226
|
+
test("数据类型覆盖: 数组中包含不同类型元素", () => {
|
|
227
|
+
const data = {
|
|
228
|
+
// 混合类型数组(JSON 支持)
|
|
229
|
+
mixed: [1, "string", true, null, { key: "value" }],
|
|
230
|
+
// 嵌套数组
|
|
231
|
+
nested: [
|
|
232
|
+
[1, 2],
|
|
233
|
+
[3, 4],
|
|
234
|
+
[5, 6]
|
|
235
|
+
],
|
|
236
|
+
// 空数组
|
|
237
|
+
empty: []
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const serialized = serializeArrayFields(data);
|
|
241
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
242
|
+
|
|
243
|
+
expect(deserialized.mixed).toEqual(data.mixed);
|
|
244
|
+
expect(deserialized.nested).toEqual(data.nested);
|
|
245
|
+
expect(deserialized.empty).toEqual([]);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// ==================== 实际使用场景模拟 ====================
|
|
249
|
+
|
|
250
|
+
test("实际场景: 角色权限数据", () => {
|
|
251
|
+
const roleData = {
|
|
252
|
+
id: 1,
|
|
253
|
+
name: "管理员",
|
|
254
|
+
code: "admin",
|
|
255
|
+
menus: [1, 2, 3, 10, 11, 12, 20, 21], // array_number_string
|
|
256
|
+
apis: [100, 101, 102, 200, 201, 202] // array_number_string
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// 模拟写入数据库
|
|
260
|
+
const serialized = serializeArrayFields(roleData);
|
|
261
|
+
expect(serialized.menus).toBe("[1,2,3,10,11,12,20,21]");
|
|
262
|
+
expect(serialized.apis).toBe("[100,101,102,200,201,202]");
|
|
263
|
+
|
|
264
|
+
// 模拟从数据库查询
|
|
265
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
266
|
+
expect(deserialized.menus).toEqual(roleData.menus);
|
|
267
|
+
expect(deserialized.apis).toEqual(roleData.apis);
|
|
268
|
+
|
|
269
|
+
// 验证可以直接使用数组方法
|
|
270
|
+
expect(deserialized.menus.includes(1)).toBe(true);
|
|
271
|
+
expect(deserialized.apis.length).toBe(6);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("实际场景: 文章标签和分类", () => {
|
|
275
|
+
const articleData = {
|
|
276
|
+
id: 1,
|
|
277
|
+
title: "测试文章",
|
|
278
|
+
tags: ["JavaScript", "TypeScript", "Bun"], // array_string
|
|
279
|
+
categoryIds: [1, 3, 5], // array_number_string
|
|
280
|
+
relatedIds: Array.from({ length: 20 }, (_, i) => i + 100) // array_number_text
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const serialized = serializeArrayFields(articleData);
|
|
284
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
285
|
+
|
|
286
|
+
expect(deserialized.tags).toEqual(articleData.tags);
|
|
287
|
+
expect(deserialized.categoryIds).toEqual(articleData.categoryIds);
|
|
288
|
+
expect(deserialized.relatedIds).toEqual(articleData.relatedIds);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// ==================== 完整流程测试 ====================
|
|
292
|
+
|
|
293
|
+
test("完整流程: 批量插入场景", () => {
|
|
294
|
+
const roles = [
|
|
295
|
+
{ name: "管理员", menus: [1, 2, 3], apis: [10, 20] },
|
|
296
|
+
{ name: "编辑", menus: [2, 3], apis: [20] },
|
|
297
|
+
{ name: "访客", menus: [3], apis: [] }
|
|
298
|
+
];
|
|
299
|
+
|
|
300
|
+
// 模拟批量序列化
|
|
301
|
+
const serializedRoles = roles.map((role) => serializeArrayFields(role));
|
|
302
|
+
|
|
303
|
+
expect(serializedRoles[0].menus).toBe("[1,2,3]");
|
|
304
|
+
expect(serializedRoles[1].menus).toBe("[2,3]");
|
|
305
|
+
expect(serializedRoles[2].menus).toBe("[3]");
|
|
306
|
+
expect(serializedRoles[2].apis).toBe("[]");
|
|
307
|
+
|
|
308
|
+
// 模拟批量反序列化
|
|
309
|
+
const deserializedRoles = serializedRoles.map((role) => deserializeArrayFields(role));
|
|
310
|
+
|
|
311
|
+
expect(deserializedRoles[0].menus).toEqual([1, 2, 3]);
|
|
312
|
+
expect(deserializedRoles[1].menus).toEqual([2, 3]);
|
|
313
|
+
expect(deserializedRoles[2].menus).toEqual([3]);
|
|
314
|
+
expect(deserializedRoles[2].apis).toEqual([]);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 测试数组类型的自动序列化和反序列化
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
|
+
|
|
7
|
+
describe("数组类型序列化/反序列化", () => {
|
|
8
|
+
// 模拟 serializeArrayFields 方法
|
|
9
|
+
const serializeArrayFields = (data: Record<string, any>): Record<string, any> => {
|
|
10
|
+
const serialized = { ...data };
|
|
11
|
+
|
|
12
|
+
for (const [key, value] of Object.entries(serialized)) {
|
|
13
|
+
if (value === null || value === undefined) continue;
|
|
14
|
+
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
serialized[key] = JSON.stringify(value);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return serialized;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// 模拟 deserializeArrayFields 方法
|
|
24
|
+
const deserializeArrayFields = <T = any>(data: Record<string, any> | null): T | null => {
|
|
25
|
+
if (!data) return null;
|
|
26
|
+
|
|
27
|
+
const deserialized = { ...data };
|
|
28
|
+
|
|
29
|
+
for (const [key, value] of Object.entries(deserialized)) {
|
|
30
|
+
if (typeof value !== "string") continue;
|
|
31
|
+
|
|
32
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(value);
|
|
35
|
+
if (Array.isArray(parsed)) {
|
|
36
|
+
deserialized[key] = parsed;
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
// 解析失败则保持原值
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return deserialized as T;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// ==================== 序列化测试 ====================
|
|
48
|
+
|
|
49
|
+
test("序列化:数字数组应转换为 JSON 字符串", () => {
|
|
50
|
+
const data = {
|
|
51
|
+
name: "test",
|
|
52
|
+
menuIds: [1, 2, 3, 4, 5],
|
|
53
|
+
apiIds: [10, 20, 30]
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const result = serializeArrayFields(data);
|
|
57
|
+
|
|
58
|
+
expect(result.name).toBe("test");
|
|
59
|
+
expect(result.menuIds).toBe("[1,2,3,4,5]");
|
|
60
|
+
expect(result.apiIds).toBe("[10,20,30]");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("序列化:字符串数组应转换为 JSON 字符串", () => {
|
|
64
|
+
const data = {
|
|
65
|
+
tags: ["tag1", "tag2", "tag3"]
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const result = serializeArrayFields(data);
|
|
69
|
+
expect(result.tags).toBe('["tag1","tag2","tag3"]');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('序列化:空数组应转换为 "[]"', () => {
|
|
73
|
+
const data = {
|
|
74
|
+
menuIds: []
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const result = serializeArrayFields(data);
|
|
78
|
+
expect(result.menuIds).toBe("[]");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("序列化:null 和 undefined 应保持不变", () => {
|
|
82
|
+
const data = {
|
|
83
|
+
menuIds: null,
|
|
84
|
+
apiIds: undefined,
|
|
85
|
+
name: "test"
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const result = serializeArrayFields(data);
|
|
89
|
+
expect(result.menuIds).toBeNull();
|
|
90
|
+
expect(result.apiIds).toBeUndefined();
|
|
91
|
+
expect(result.name).toBe("test");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("序列化:非数组字段应保持不变", () => {
|
|
95
|
+
const data = {
|
|
96
|
+
name: "test",
|
|
97
|
+
age: 25,
|
|
98
|
+
active: true,
|
|
99
|
+
menuIds: [1, 2, 3]
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const result = serializeArrayFields(data);
|
|
103
|
+
expect(result.name).toBe("test");
|
|
104
|
+
expect(result.age).toBe(25);
|
|
105
|
+
expect(result.active).toBe(true);
|
|
106
|
+
expect(result.menuIds).toBe("[1,2,3]");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// ==================== 反序列化测试 ====================
|
|
110
|
+
|
|
111
|
+
test("反序列化:JSON 数字数组字符串应转换为数组", () => {
|
|
112
|
+
const data = {
|
|
113
|
+
name: "test",
|
|
114
|
+
menuIds: "[1,2,3,4,5]",
|
|
115
|
+
apiIds: "[10,20,30]"
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const result = deserializeArrayFields(data);
|
|
119
|
+
|
|
120
|
+
expect(result.name).toBe("test");
|
|
121
|
+
expect(result.menuIds).toEqual([1, 2, 3, 4, 5]);
|
|
122
|
+
expect(result.apiIds).toEqual([10, 20, 30]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("反序列化:JSON 字符串数组应转换为数组", () => {
|
|
126
|
+
const data = {
|
|
127
|
+
tags: '["tag1","tag2","tag3"]'
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const result = deserializeArrayFields(data);
|
|
131
|
+
expect(result.tags).toEqual(["tag1", "tag2", "tag3"]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('反序列化:"[]" 应转换为空数组', () => {
|
|
135
|
+
const data = {
|
|
136
|
+
menuIds: "[]"
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const result = deserializeArrayFields(data);
|
|
140
|
+
expect(result.menuIds).toEqual([]);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("反序列化:非 JSON 数组字符串应保持不变", () => {
|
|
144
|
+
const data = {
|
|
145
|
+
name: "test",
|
|
146
|
+
description: "This is a [test] string",
|
|
147
|
+
url: "https://example.com"
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const result = deserializeArrayFields(data);
|
|
151
|
+
expect(result.name).toBe("test");
|
|
152
|
+
expect(result.description).toBe("This is a [test] string");
|
|
153
|
+
expect(result.url).toBe("https://example.com");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("反序列化:无效的 JSON 字符串应保持不变", () => {
|
|
157
|
+
const data = {
|
|
158
|
+
menuIds: "[1,2,3,", // 无效 JSON
|
|
159
|
+
apiIds: "[invalid]"
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const result = deserializeArrayFields(data);
|
|
163
|
+
expect(result.menuIds).toBe("[1,2,3,");
|
|
164
|
+
expect(result.apiIds).toBe("[invalid]");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("反序列化:非字符串值应保持不变", () => {
|
|
168
|
+
const data = {
|
|
169
|
+
name: "test",
|
|
170
|
+
age: 25,
|
|
171
|
+
active: true,
|
|
172
|
+
count: null
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const result = deserializeArrayFields(data);
|
|
176
|
+
expect(result.name).toBe("test");
|
|
177
|
+
expect(result.age).toBe(25);
|
|
178
|
+
expect(result.active).toBe(true);
|
|
179
|
+
expect(result.count).toBeNull();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("反序列化:null 数据应返回 null", () => {
|
|
183
|
+
const result = deserializeArrayFields(null);
|
|
184
|
+
expect(result).toBeNull();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// ==================== 往返测试 ====================
|
|
188
|
+
|
|
189
|
+
test("往返:序列化后反序列化应得到原始数据", () => {
|
|
190
|
+
const originalData = {
|
|
191
|
+
name: "test role",
|
|
192
|
+
menuIds: [1, 2, 3, 4, 5],
|
|
193
|
+
apiIds: [10, 20, 30, 40],
|
|
194
|
+
tags: ["tag1", "tag2"],
|
|
195
|
+
emptyArray: []
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const serialized = serializeArrayFields(originalData);
|
|
199
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
200
|
+
|
|
201
|
+
expect(deserialized.name).toBe(originalData.name);
|
|
202
|
+
expect(deserialized.menuIds).toEqual(originalData.menuIds);
|
|
203
|
+
expect(deserialized.apiIds).toEqual(originalData.apiIds);
|
|
204
|
+
expect(deserialized.tags).toEqual(originalData.tags);
|
|
205
|
+
expect(deserialized.emptyArray).toEqual(originalData.emptyArray);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// ==================== 边界情况测试 ====================
|
|
209
|
+
|
|
210
|
+
test("边界情况:包含特殊字符的字符串数组", () => {
|
|
211
|
+
const data = {
|
|
212
|
+
items: ['item"1', "item'2", "item[3]", "item,4"]
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const serialized = serializeArrayFields(data);
|
|
216
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
217
|
+
|
|
218
|
+
expect(deserialized.items).toEqual(data.items);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("边界情况:嵌套数组(JSON 支持)", () => {
|
|
222
|
+
const data = {
|
|
223
|
+
nested: [
|
|
224
|
+
[1, 2],
|
|
225
|
+
[3, 4],
|
|
226
|
+
[5, 6]
|
|
227
|
+
]
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const serialized = serializeArrayFields(data);
|
|
231
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
232
|
+
|
|
233
|
+
expect(deserialized.nested).toEqual(data.nested);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("边界情况:大数组", () => {
|
|
237
|
+
const data = {
|
|
238
|
+
largeArray: Array.from({ length: 1000 }, (_, i) => i)
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const serialized = serializeArrayFields(data);
|
|
242
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
243
|
+
|
|
244
|
+
expect(deserialized.largeArray).toEqual(data.largeArray);
|
|
245
|
+
expect(deserialized.largeArray.length).toBe(1000);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("边界情况:混合类型数组(JSON 支持)", () => {
|
|
249
|
+
const data = {
|
|
250
|
+
mixed: [1, "two", true, null, { key: "value" }]
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const serialized = serializeArrayFields(data);
|
|
254
|
+
const deserialized = deserializeArrayFields(serialized);
|
|
255
|
+
|
|
256
|
+
expect(deserialized.mixed).toEqual(data.mixed);
|
|
257
|
+
});
|
|
258
|
+
});
|