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.
Files changed (79) hide show
  1. package/.gitignore +0 -0
  2. package/README.md +10 -13
  3. package/configs/presetFields.ts +10 -0
  4. package/configs/presetRegexp.ts +225 -0
  5. package/docs/README.md +17 -11
  6. package/docs/api/api.md +15 -1
  7. package/docs/guide/quickstart.md +19 -5
  8. package/docs/infra/redis.md +23 -11
  9. package/docs/quickstart.md +5 -335
  10. package/docs/reference/addon.md +0 -15
  11. package/docs/reference/config.md +1 -1
  12. package/docs/reference/logger.md +3 -3
  13. package/docs/reference/sync.md +99 -73
  14. package/docs/reference/table.md +1 -1
  15. package/package.json +15 -16
  16. package/docs/cipher.md +0 -582
  17. package/docs/database.md +0 -1176
  18. package/tests/_mocks/mockSqliteDb.ts +0 -204
  19. package/tests/addonHelper-cache.test.ts +0 -32
  20. package/tests/api-integration-array-number.test.ts +0 -282
  21. package/tests/apiHandler-routePath-only.test.ts +0 -32
  22. package/tests/befly-config-env.test.ts +0 -78
  23. package/tests/cacheHelper.test.ts +0 -323
  24. package/tests/cacheKeys.test.ts +0 -41
  25. package/tests/checkApi-routePath-strict.test.ts +0 -166
  26. package/tests/checkMenu.test.ts +0 -346
  27. package/tests/checkTable-smoke.test.ts +0 -157
  28. package/tests/cipher.test.ts +0 -249
  29. package/tests/dbDialect-cache.test.ts +0 -23
  30. package/tests/dbDialect.test.ts +0 -46
  31. package/tests/dbHelper-advanced.test.ts +0 -723
  32. package/tests/dbHelper-all-array-types.test.ts +0 -316
  33. package/tests/dbHelper-array-serialization.test.ts +0 -258
  34. package/tests/dbHelper-batch-write.test.ts +0 -90
  35. package/tests/dbHelper-columns.test.ts +0 -234
  36. package/tests/dbHelper-execute.test.ts +0 -187
  37. package/tests/dbHelper-joins.test.ts +0 -221
  38. package/tests/fields-redis-cache.test.ts +0 -127
  39. package/tests/fields-validate.test.ts +0 -99
  40. package/tests/fixtures/scanFilesAddon/node_modules/@befly-addon/demo/apis/sub/b.ts +0 -3
  41. package/tests/fixtures/scanFilesApis/a.ts +0 -3
  42. package/tests/fixtures/scanFilesApis/sub/b.ts +0 -3
  43. package/tests/getClientIp.test.ts +0 -54
  44. package/tests/integration.test.ts +0 -189
  45. package/tests/jwt.test.ts +0 -65
  46. package/tests/loadPlugins-order-smoke.test.ts +0 -75
  47. package/tests/logger.test.ts +0 -325
  48. package/tests/redisHelper.test.ts +0 -495
  49. package/tests/redisKeys.test.ts +0 -9
  50. package/tests/scanConfig.test.ts +0 -144
  51. package/tests/scanFiles-routePath.test.ts +0 -46
  52. package/tests/smoke-sql.test.ts +0 -24
  53. package/tests/sqlBuilder-advanced.test.ts +0 -608
  54. package/tests/sqlBuilder.test.ts +0 -209
  55. package/tests/sync-connection.test.ts +0 -183
  56. package/tests/sync-init-guard.test.ts +0 -105
  57. package/tests/syncApi-insBatch-fields-consistent.test.ts +0 -61
  58. package/tests/syncApi-obsolete-records.test.ts +0 -69
  59. package/tests/syncApi-type-compat.test.ts +0 -72
  60. package/tests/syncDev-permissions.test.ts +0 -81
  61. package/tests/syncMenu-disableMenus-hard-delete.test.ts +0 -88
  62. package/tests/syncMenu-duplicate-path.test.ts +0 -122
  63. package/tests/syncMenu-obsolete-records.test.ts +0 -161
  64. package/tests/syncMenu-parentPath-from-tree.test.ts +0 -75
  65. package/tests/syncMenu-paths.test.ts +0 -59
  66. package/tests/syncTable-apply.test.ts +0 -279
  67. package/tests/syncTable-array-number.test.ts +0 -160
  68. package/tests/syncTable-constants.test.ts +0 -101
  69. package/tests/syncTable-db-integration.test.ts +0 -237
  70. package/tests/syncTable-ddl.test.ts +0 -245
  71. package/tests/syncTable-helpers.test.ts +0 -99
  72. package/tests/syncTable-schema.test.ts +0 -99
  73. package/tests/syncTable-testkit.test.ts +0 -25
  74. package/tests/syncTable-types.test.ts +0 -122
  75. package/tests/tableRef-and-deserialize.test.ts +0 -67
  76. package/tests/util.test.ts +0 -100
  77. package/tests/validator-array-number.test.ts +0 -310
  78. package/tests/validator-default.test.ts +0 -373
  79. package/tests/validator.test.ts +0 -679
@@ -1,316 +0,0 @@
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: ["/dashboard", "/permission/role"], // array_text
256
- apis: ["/api/login", "/api/user/list"] // array_text
257
- };
258
-
259
- // 模拟写入数据库
260
- const serialized = serializeArrayFields(roleData);
261
- expect(serialized.menus).toBe('["/dashboard","/permission/role"]');
262
- expect(serialized.apis).toBe('["/api/login","/api/user/list"]');
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("/dashboard")).toBe(true);
271
- expect(deserialized.apis.length).toBe(2);
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: ["/dashboard", "/permission/role"], apis: ["/api/login", "/api/user/list"] },
296
- { name: "编辑", menus: ["/dashboard"], apis: ["/api/user/list"] },
297
- { name: "访客", menus: ["/dashboard"], apis: [] }
298
- ];
299
-
300
- // 模拟批量序列化
301
- const serializedRoles = roles.map((role) => serializeArrayFields(role));
302
-
303
- expect(serializedRoles[0].menus).toBe('["/dashboard","/permission/role"]');
304
- expect(serializedRoles[1].menus).toBe('["/dashboard"]');
305
- expect(serializedRoles[2].menus).toBe('["/dashboard"]');
306
- expect(serializedRoles[2].apis).toBe("[]");
307
-
308
- // 模拟批量反序列化
309
- const deserializedRoles = serializedRoles.map((role) => deserializeArrayFields(role));
310
-
311
- expect(deserializedRoles[0].menus).toEqual(["/dashboard", "/permission/role"]);
312
- expect(deserializedRoles[1].menus).toEqual(["/dashboard"]);
313
- expect(deserializedRoles[2].menus).toEqual(["/dashboard"]);
314
- expect(deserializedRoles[2].apis).toEqual([]);
315
- });
316
- });
@@ -1,258 +0,0 @@
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
- });
@@ -1,90 +0,0 @@
1
- import { describe, expect, it, mock } from "bun:test";
2
-
3
- import { MySqlDialect } from "../lib/dbDialect.js";
4
- import { DbHelper } from "../lib/dbHelper.js";
5
-
6
- function createRedisMock() {
7
- return {
8
- getObject: async () => null,
9
- setObject: async () => "OK",
10
- genTimeID: async () => 1
11
- };
12
- }
13
-
14
- describe("DbHelper - batch write helpers", () => {
15
- it("delForceBatch: 空数组直接返回 0 且不执行 SQL", async () => {
16
- const sqlMock = {
17
- unsafe: mock(() => Promise.resolve({ changes: 0 }))
18
- };
19
-
20
- const dbHelper = new DbHelper({ redis: createRedisMock() as any, sql: sqlMock, dialect: new MySqlDialect() });
21
-
22
- const changes = await (dbHelper as any).delForceBatch("addon_admin_api", []);
23
-
24
- expect(changes).toBe(0);
25
- expect(sqlMock.unsafe).toHaveBeenCalledTimes(0);
26
- });
27
-
28
- it("delForceBatch: 生成单条 DELETE ... IN 并执行一次", async () => {
29
- const sqlMock = {
30
- unsafe: mock(() => Promise.resolve({ changes: 2 }))
31
- };
32
-
33
- const dbHelper = new DbHelper({ redis: createRedisMock() as any, sql: sqlMock, dialect: new MySqlDialect() });
34
-
35
- const changes = await (dbHelper as any).delForceBatch("addon_admin_api", [1, 2]);
36
-
37
- expect(changes).toBe(2);
38
- expect(sqlMock.unsafe).toHaveBeenCalledTimes(1);
39
-
40
- const call = (sqlMock.unsafe as any).mock.calls[0];
41
- expect(call[0]).toBe("DELETE FROM `addon_admin_api` WHERE `id` IN (?,?)");
42
- expect(call[1]).toEqual([1, 2]);
43
- });
44
-
45
- it("updBatch: 空数组直接返回 0 且不执行 SQL", async () => {
46
- const sqlMock = {
47
- unsafe: mock(() => Promise.resolve({ changes: 0 }))
48
- };
49
-
50
- const dbHelper = new DbHelper({ redis: createRedisMock() as any, sql: sqlMock, dialect: new MySqlDialect() });
51
-
52
- const changes = await (dbHelper as any).updBatch("addon_admin_api", []);
53
-
54
- expect(changes).toBe(0);
55
- expect(sqlMock.unsafe).toHaveBeenCalledTimes(0);
56
- });
57
-
58
- it("updBatch: 生成单条 UPDATE + CASE 并执行一次", async () => {
59
- const sqlMock = {
60
- unsafe: mock(() => Promise.resolve({ changes: 2 }))
61
- };
62
-
63
- const dbHelper = new DbHelper({ redis: createRedisMock() as any, sql: sqlMock, dialect: new MySqlDialect() });
64
-
65
- const changes = await (dbHelper as any).updBatch("addon_admin_api", [
66
- { id: 1, data: { name: "A", routePath: "/api/a", addonName: "x" } },
67
- { id: 2, data: { name: "B", routePath: "/api/b", addonName: "y" } }
68
- ]);
69
-
70
- expect(changes).toBe(2);
71
- expect(sqlMock.unsafe).toHaveBeenCalledTimes(1);
72
-
73
- const call = (sqlMock.unsafe as any).mock.calls[0];
74
- const sql = String(call[0]);
75
- const params = call[1] as any[];
76
-
77
- expect(sql.startsWith("UPDATE `addon_admin_api` SET")).toBe(true);
78
- expect(sql.includes("`name` = CASE `id`")).toBe(true);
79
- expect(sql.includes("`route_path` = CASE `id`")).toBe(true);
80
- expect(sql.includes("`addon_name` = CASE `id`")).toBe(true);
81
- expect(sql.includes("`updated_at` = ?")).toBe(true);
82
- expect(sql.endsWith("WHERE `id` IN (?,?) AND `state` > 0")).toBe(true);
83
-
84
- // 参数数量应包含:每个字段 2 行 * (id,value) *3字段 => 12,+ updated_at 1,+ where ids 2 => 15
85
- expect(params.length).toBe(15);
86
- // 末尾两个参数是 where ids
87
- expect(params[params.length - 2]).toBe(1);
88
- expect(params[params.length - 1]).toBe(2);
89
- });
90
- });