befly 3.8.30 → 3.8.32
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 +83 -0
- package/{config.ts → befly.config.ts} +26 -6
- package/checks/checkApp.ts +31 -1
- package/hooks/cors.ts +3 -3
- package/hooks/parser.ts +3 -3
- package/hooks/validator.ts +1 -1
- package/lib/cacheHelper.ts +0 -6
- package/lib/cipher.ts +2 -1
- package/lib/connect.ts +17 -19
- package/lib/jwt.ts +1 -1
- package/lib/logger.ts +1 -1
- package/lib/validator.ts +149 -384
- package/loader/loadHooks.ts +4 -3
- package/loader/loadPlugins.ts +7 -9
- package/main.ts +22 -36
- package/package.json +6 -5
- package/plugins/cipher.ts +1 -1
- package/plugins/config.ts +3 -4
- package/plugins/db.ts +4 -5
- package/plugins/jwt.ts +3 -2
- package/plugins/logger.ts +6 -6
- package/plugins/redis.ts +8 -12
- package/router/static.ts +3 -6
- package/sync/syncAll.ts +7 -12
- package/sync/syncApi.ts +4 -3
- package/sync/syncDb.ts +6 -5
- package/sync/syncDev.ts +9 -8
- package/sync/syncMenu.ts +174 -132
- package/tests/integration.test.ts +2 -6
- package/tests/redisHelper.test.ts +1 -2
- package/tests/validator.test.ts +611 -85
- package/types/befly.d.ts +7 -0
- package/types/cache.d.ts +73 -0
- package/types/common.d.ts +1 -37
- package/types/database.d.ts +5 -0
- package/types/index.ts +5 -5
- package/types/plugin.d.ts +1 -4
- package/types/redis.d.ts +37 -2
- package/types/table.d.ts +6 -44
- package/util.ts +283 -0
- package/tests/validator-advanced.test.ts +0 -653
- package/types/addon.d.ts +0 -50
- package/types/crypto.d.ts +0 -23
- package/types/jwt.d.ts +0 -99
- package/types/logger.d.ts +0 -13
- package/types/tool.d.ts +0 -67
- package/types/validator.d.ts +0 -43
|
@@ -1,653 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validator 高级测试用例
|
|
3
|
-
* 测试边界条件、正则表达式、类型转换、错误消息
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, test, expect } from 'bun:test';
|
|
7
|
-
import { Validator } from '../lib/validator';
|
|
8
|
-
|
|
9
|
-
const validator = new Validator();
|
|
10
|
-
|
|
11
|
-
describe('Validator - 字段类型详细测试', () => {
|
|
12
|
-
test('string 类型 - 空字符串应如何处理', () => {
|
|
13
|
-
const data = { name: '' };
|
|
14
|
-
const rules = { name: { name: '名称', type: 'string', min: 2, max: 10 } };
|
|
15
|
-
const required = ['name'];
|
|
16
|
-
|
|
17
|
-
const result = validator.validate(data, rules, required);
|
|
18
|
-
|
|
19
|
-
// 空字符串在 required 检查时应该失败
|
|
20
|
-
expect(result.code).toBe(1);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test('string 类型 - 只包含空格应失败', () => {
|
|
24
|
-
const data = { name: ' ' };
|
|
25
|
-
const rules = { name: { name: '名称', type: 'string', min: 2, max: 10 } };
|
|
26
|
-
|
|
27
|
-
const result = validator.validate(data, rules);
|
|
28
|
-
|
|
29
|
-
// **问题**:只有空格的字符串应该被视为无效
|
|
30
|
-
console.log('只包含空格的验证结果:', result);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('number 类型 - 字符串数字应如何处理', () => {
|
|
34
|
-
const data = { age: '25' }; // 字符串而非数字
|
|
35
|
-
const rules = { age: { name: '年龄', type: 'number', min: 0, max: 150 } };
|
|
36
|
-
|
|
37
|
-
const result = validator.validate(data, rules);
|
|
38
|
-
|
|
39
|
-
// **问题**:是否应该自动转换 '25' -> 25?
|
|
40
|
-
console.log('字符串数字的验证结果:', result);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('number 类型 - 浮点数应如何处理', () => {
|
|
44
|
-
const data = { price: 19.99 };
|
|
45
|
-
const rules = { price: { name: '价格', type: 'number', min: 0, max: 10000 } };
|
|
46
|
-
|
|
47
|
-
const result = validator.validate(data, rules);
|
|
48
|
-
|
|
49
|
-
expect(result.code).toBe(0);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('number 类型 - NaN 应失败', () => {
|
|
53
|
-
const data = { age: NaN };
|
|
54
|
-
const rules = { age: { name: '年龄', type: 'number' } };
|
|
55
|
-
|
|
56
|
-
const result = validator.validate(data, rules);
|
|
57
|
-
|
|
58
|
-
expect(result.code).toBe(1);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('number 类型 - Infinity 应失败', () => {
|
|
62
|
-
const data = { value: Infinity };
|
|
63
|
-
const rules = { value: { name: '值', type: 'number' } };
|
|
64
|
-
|
|
65
|
-
const result = validator.validate(data, rules);
|
|
66
|
-
|
|
67
|
-
expect(result.code).toBe(1);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('number 类型 - 0 应允许', () => {
|
|
71
|
-
const data = { count: 0 };
|
|
72
|
-
const rules = { count: { name: '计数', type: 'number', min: 0, max: 100 } };
|
|
73
|
-
|
|
74
|
-
const result = validator.validate(data, rules);
|
|
75
|
-
|
|
76
|
-
expect(result.code).toBe(0);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test('number 类型 - 负数范围验证', () => {
|
|
80
|
-
const data = { temperature: -10 };
|
|
81
|
-
const rules = { temperature: { name: '温度', type: 'number', min: -50, max: 50 } };
|
|
82
|
-
|
|
83
|
-
const result = validator.validate(data, rules);
|
|
84
|
-
|
|
85
|
-
expect(result.code).toBe(0);
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe('Validator - 长度验证详细测试', () => {
|
|
90
|
-
test('string 最小长度 - 边界值', () => {
|
|
91
|
-
const cases = [
|
|
92
|
-
{ value: 'a', min: 2, shouldPass: false },
|
|
93
|
-
{ value: 'ab', min: 2, shouldPass: true },
|
|
94
|
-
{ value: 'abc', min: 2, shouldPass: true }
|
|
95
|
-
];
|
|
96
|
-
|
|
97
|
-
cases.forEach(({ value, min, shouldPass }) => {
|
|
98
|
-
const data = { name: value };
|
|
99
|
-
const rules = { name: { name: '名称', type: 'string', min: min, max: 100 } };
|
|
100
|
-
const result = validator.validate(data, rules);
|
|
101
|
-
|
|
102
|
-
expect(result.code).toBe(shouldPass ? 0 : 1);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test('string 最大长度 - 边界值', () => {
|
|
107
|
-
const cases = [
|
|
108
|
-
{ value: 'abc', max: 5, shouldPass: true },
|
|
109
|
-
{ value: 'abcde', max: 5, shouldPass: true },
|
|
110
|
-
{ value: 'abcdef', max: 5, shouldPass: false }
|
|
111
|
-
];
|
|
112
|
-
|
|
113
|
-
cases.forEach(({ value, max, shouldPass }) => {
|
|
114
|
-
const data = { name: value };
|
|
115
|
-
const rules = { name: { name: '名称', type: 'string', min: 0, max: max } };
|
|
116
|
-
const result = validator.validate(data, rules);
|
|
117
|
-
|
|
118
|
-
expect(result.code).toBe(shouldPass ? 0 : 1);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test('中文字符长度计算', () => {
|
|
123
|
-
// **问题**:中文字符是按字符数还是字节数?
|
|
124
|
-
const data = { content: '你好世界' }; // 4 个字符
|
|
125
|
-
const rules = { content: { name: '内容', type: 'string', min: 0, max: 5 } };
|
|
126
|
-
|
|
127
|
-
const result = validator.validate(data, rules);
|
|
128
|
-
|
|
129
|
-
// 应该按字符数计算(4 个字符)
|
|
130
|
-
expect(result.code).toBe(0);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test('Emoji 字符长度计算', () => {
|
|
134
|
-
// **问题**:Emoji 可能占 2 个或更多字符位
|
|
135
|
-
const data = { message: '👋🌍' }; // 2 个 emoji
|
|
136
|
-
const rules = { message: { name: '消息', type: 'string', min: 0, max: 5 } };
|
|
137
|
-
|
|
138
|
-
const result = validator.validate(data, rules);
|
|
139
|
-
|
|
140
|
-
console.log('Emoji 长度计算结果:', result);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test('number 范围验证 - 边界值', () => {
|
|
144
|
-
const cases = [
|
|
145
|
-
{ value: 0, min: 0, max: 100, shouldPass: true },
|
|
146
|
-
{ value: 100, min: 0, max: 100, shouldPass: true },
|
|
147
|
-
{ value: -1, min: 0, max: 100, shouldPass: false },
|
|
148
|
-
{ value: 101, min: 0, max: 100, shouldPass: false }
|
|
149
|
-
];
|
|
150
|
-
|
|
151
|
-
cases.forEach(({ value, min, max, shouldPass }) => {
|
|
152
|
-
const data = { age: value };
|
|
153
|
-
const rules = { age: { name: '年龄', type: 'number', min: min, max: max } };
|
|
154
|
-
const result = validator.validate(data, rules);
|
|
155
|
-
|
|
156
|
-
expect(result.code).toBe(shouldPass ? 0 : 1);
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
describe('Validator - 正则表达式详细测试', () => {
|
|
162
|
-
test('email 验证 - 有效格式', () => {
|
|
163
|
-
const validEmails = ['test@example.com', 'user.name@domain.co.uk', 'admin+tag@site.org'];
|
|
164
|
-
|
|
165
|
-
validEmails.forEach((email) => {
|
|
166
|
-
const data = { email: email };
|
|
167
|
-
const rules = { email: { name: '邮箱', type: 'string', regexp: '@email' } };
|
|
168
|
-
const result = validator.validate(data, rules);
|
|
169
|
-
|
|
170
|
-
expect(result.code).toBe(0);
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test('email 验证 - 无效格式', () => {
|
|
175
|
-
const invalidEmails = ['plaintext', '@example.com', 'user@', 'user @domain.com', 'user@domain'];
|
|
176
|
-
|
|
177
|
-
invalidEmails.forEach((email) => {
|
|
178
|
-
const data = { email: email };
|
|
179
|
-
const rules = { email: { name: '邮箱', type: 'string', regexp: '@email' } };
|
|
180
|
-
const result = validator.validate(data, rules);
|
|
181
|
-
|
|
182
|
-
expect(result.code).toBe(1);
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test('phone 验证 - 有效手机号', () => {
|
|
187
|
-
const validPhones = ['13800138000', '15012345678', '18888888888'];
|
|
188
|
-
|
|
189
|
-
validPhones.forEach((phone) => {
|
|
190
|
-
const data = { phone: phone };
|
|
191
|
-
const rules = { phone: { name: '手机号', type: 'string', regexp: '@phone' } };
|
|
192
|
-
const result = validator.validate(data, rules);
|
|
193
|
-
|
|
194
|
-
expect(result.code).toBe(0);
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test('phone 验证 - 无效手机号', () => {
|
|
199
|
-
const invalidPhones = [
|
|
200
|
-
'12345678901', // 首位不是1
|
|
201
|
-
'10012345678', // 第二位不在3-9
|
|
202
|
-
'1381234567', // 长度不足
|
|
203
|
-
'138123456789', // 长度超出
|
|
204
|
-
'abcdefghijk' // 包含字母
|
|
205
|
-
];
|
|
206
|
-
|
|
207
|
-
invalidPhones.forEach((phone) => {
|
|
208
|
-
const data = { phone: phone };
|
|
209
|
-
const rules = { phone: { name: '手机号', type: 'string', regexp: '@phone' } };
|
|
210
|
-
const result = validator.validate(data, rules);
|
|
211
|
-
|
|
212
|
-
expect(result.code).toBe(1);
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test('url 验证', () => {
|
|
217
|
-
const validUrls = ['http://example.com', 'https://www.site.org/path', 'https://sub.domain.co.uk/page?q=1'];
|
|
218
|
-
|
|
219
|
-
validUrls.forEach((url) => {
|
|
220
|
-
const data = { website: url };
|
|
221
|
-
const rules = { website: { name: '网址', type: 'string', regexp: '@url' } };
|
|
222
|
-
const result = validator.validate(data, rules);
|
|
223
|
-
|
|
224
|
-
expect(result.code).toBe(0);
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
test('自定义正则 - 纯字母', () => {
|
|
229
|
-
const data1 = { code: 'ABC123' }; // 包含数字
|
|
230
|
-
const data2 = { code: 'ABCDEF' }; // 纯字母
|
|
231
|
-
|
|
232
|
-
const rules = { code: { name: '代码', type: 'string', regexp: '^[A-Z]+$' } };
|
|
233
|
-
|
|
234
|
-
const result1 = validator.validate(data1, rules);
|
|
235
|
-
const result2 = validator.validate(data2, rules);
|
|
236
|
-
|
|
237
|
-
expect(result1.code).toBe(1);
|
|
238
|
-
expect(result2.code).toBe(0);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
test('自定义正则 - 转义字符处理', () => {
|
|
242
|
-
// **问题**:正则中的转义字符是否正确处理
|
|
243
|
-
const data = { code: '123' };
|
|
244
|
-
const rules = { code: { name: '代码', type: 'string', regexp: '^\\d+$' } };
|
|
245
|
-
|
|
246
|
-
const result = validator.validate(data, rules);
|
|
247
|
-
|
|
248
|
-
expect(result.code).toBe(0);
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
test('正则别名大小写敏感', () => {
|
|
252
|
-
const data = { email: 'test@example.com' };
|
|
253
|
-
const rules1 = { email: { name: '邮箱', type: 'string', regexp: '@email' } };
|
|
254
|
-
const rules2 = { email: { name: '邮箱', type: 'string', regexp: '@EMAIL' } };
|
|
255
|
-
|
|
256
|
-
const result1 = validator.validate(data, rules1);
|
|
257
|
-
const result2 = validator.validate(data, rules2);
|
|
258
|
-
|
|
259
|
-
// @email 能识别别名,@EMAIL 不能识别
|
|
260
|
-
expect(result1.code).toBe(0);
|
|
261
|
-
expect(result2.code).toBe(1); // 大写无法识别别名
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
describe('Validator - 必填字段验证', () => {
|
|
266
|
-
test('required - 字段缺失应失败', () => {
|
|
267
|
-
const data = { name: 'john' };
|
|
268
|
-
const rules = {
|
|
269
|
-
name: { name: '姓名', type: 'string' },
|
|
270
|
-
email: { name: '邮箱', type: 'string' }
|
|
271
|
-
};
|
|
272
|
-
const required = ['email'];
|
|
273
|
-
|
|
274
|
-
const result = validator.validate(data, rules, required);
|
|
275
|
-
|
|
276
|
-
expect(result.code).toBe(1);
|
|
277
|
-
expect(result.fields.email).toBeDefined();
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
test('required - 字段为 null 应失败', () => {
|
|
281
|
-
const data = { email: null };
|
|
282
|
-
const rules = { email: { name: '邮箱', type: 'string' } };
|
|
283
|
-
const required = ['email'];
|
|
284
|
-
|
|
285
|
-
const result = validator.validate(data, rules, required);
|
|
286
|
-
|
|
287
|
-
expect(result.code).toBe(1);
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
test('required - 字段为 undefined 应失败', () => {
|
|
291
|
-
const data = { email: undefined };
|
|
292
|
-
const rules = { email: { name: '邮箱', type: 'string' } };
|
|
293
|
-
const required = ['email'];
|
|
294
|
-
|
|
295
|
-
const result = validator.validate(data, rules, required);
|
|
296
|
-
|
|
297
|
-
expect(result.code).toBe(1);
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
test('required - 字段为空字符串应失败', () => {
|
|
301
|
-
const data = { email: '' };
|
|
302
|
-
const rules = { email: { name: '邮箱', type: 'string' } };
|
|
303
|
-
const required = ['email'];
|
|
304
|
-
|
|
305
|
-
const result = validator.validate(data, rules, required);
|
|
306
|
-
|
|
307
|
-
expect(result.code).toBe(1);
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
test('required - 字段为 0 应通过', () => {
|
|
311
|
-
const data = { count: 0 };
|
|
312
|
-
const rules = { count: { name: '计数', type: 'number' } };
|
|
313
|
-
const required = ['count'];
|
|
314
|
-
|
|
315
|
-
const result = validator.validate(data, rules, required);
|
|
316
|
-
|
|
317
|
-
expect(result.code).toBe(0);
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
test('required - 字段为 false 应通过', () => {
|
|
321
|
-
const data = { enabled: false };
|
|
322
|
-
const rules = { enabled: { name: '启用', type: 'string' } };
|
|
323
|
-
const required = ['enabled'];
|
|
324
|
-
|
|
325
|
-
const result = validator.validate(data, rules, required);
|
|
326
|
-
|
|
327
|
-
// **问题**:boolean 类型应该如何验证?
|
|
328
|
-
console.log('boolean false 的验证结果:', result);
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
test('required - 多个必填字段', () => {
|
|
332
|
-
const data = { name: 'john' };
|
|
333
|
-
const rules = {
|
|
334
|
-
name: { name: '姓名', type: 'string' },
|
|
335
|
-
email: { name: '邮箱', type: 'string' },
|
|
336
|
-
phone: { name: '手机', type: 'string' }
|
|
337
|
-
};
|
|
338
|
-
const required = ['name', 'email', 'phone'];
|
|
339
|
-
|
|
340
|
-
const result = validator.validate(data, rules, required);
|
|
341
|
-
|
|
342
|
-
expect(result.code).toBe(1);
|
|
343
|
-
expect(result.fields.email).toBeDefined();
|
|
344
|
-
expect(result.fields.phone).toBeDefined();
|
|
345
|
-
});
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
describe('Validator - 错误消息测试', () => {
|
|
349
|
-
test('错误消息应包含字段名', () => {
|
|
350
|
-
const data = { age: 200 };
|
|
351
|
-
const rules = { age: { name: '年龄', type: 'number', min: 0, max: 150 } };
|
|
352
|
-
|
|
353
|
-
const result = validator.validate(data, rules);
|
|
354
|
-
|
|
355
|
-
expect(result.code).toBe(1);
|
|
356
|
-
expect(result.fields.age).toBeDefined();
|
|
357
|
-
// 错误消息应该包含 "年龄"
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
test('类型错误消息', () => {
|
|
361
|
-
const data = { age: 'abc' };
|
|
362
|
-
const rules = { age: { name: '年龄', type: 'number' } };
|
|
363
|
-
|
|
364
|
-
const result = validator.validate(data, rules);
|
|
365
|
-
|
|
366
|
-
expect(result.code).toBe(1);
|
|
367
|
-
console.log('类型错误消息:', result.fields.age);
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
test('长度错误消息', () => {
|
|
371
|
-
const data = { name: 'a' };
|
|
372
|
-
const rules = { name: { name: '名称', type: 'string', min: 2, max: 10 } };
|
|
373
|
-
|
|
374
|
-
const result = validator.validate(data, rules);
|
|
375
|
-
|
|
376
|
-
expect(result.code).toBe(1);
|
|
377
|
-
console.log('长度错误消息:', result.fields.name);
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
test('正则错误消息', () => {
|
|
381
|
-
const data = { email: 'invalid' };
|
|
382
|
-
const rules = { email: { name: '邮箱', type: 'string', regexp: '@email' } };
|
|
383
|
-
|
|
384
|
-
const result = validator.validate(data, rules);
|
|
385
|
-
|
|
386
|
-
expect(result.code).toBe(1);
|
|
387
|
-
console.log('正则错误消息:', result.fields.email);
|
|
388
|
-
});
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
describe('Validator - 参数验证', () => {
|
|
392
|
-
test('data 为 null 应报错', () => {
|
|
393
|
-
const result = validator.validate(null as any, {});
|
|
394
|
-
|
|
395
|
-
expect(result.code).toBe(1);
|
|
396
|
-
expect(result.fields.error).toContain('对象格式');
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
test('data 为数组应报错', () => {
|
|
400
|
-
const result = validator.validate([] as any, {});
|
|
401
|
-
|
|
402
|
-
expect(result.code).toBe(1);
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
test('rules 为 null 应报错', () => {
|
|
406
|
-
const result = validator.validate({}, null as any);
|
|
407
|
-
|
|
408
|
-
expect(result.code).toBe(1);
|
|
409
|
-
expect(result.fields.error).toContain('对象格式');
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
test('required 为字符串应报错', () => {
|
|
413
|
-
const result = validator.validate({}, {}, 'email' as any);
|
|
414
|
-
|
|
415
|
-
expect(result.code).toBe(1);
|
|
416
|
-
expect(result.fields.error).toContain('数组格式');
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
test('required 为 null 应使用默认值', () => {
|
|
420
|
-
const data = { name: 'john' };
|
|
421
|
-
const rules = { name: { name: '姓名', type: 'string' } };
|
|
422
|
-
|
|
423
|
-
const result = validator.validate(data, rules, null as any);
|
|
424
|
-
|
|
425
|
-
// 应该使用默认的空数组
|
|
426
|
-
console.log('required 为 null 的验证结果:', result);
|
|
427
|
-
});
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
describe('Validator - 边界条件', () => {
|
|
431
|
-
test('空规则对象应通过验证', () => {
|
|
432
|
-
const data = { name: 'john', email: 'john@example.com' };
|
|
433
|
-
const rules = {};
|
|
434
|
-
|
|
435
|
-
const result = validator.validate(data, rules);
|
|
436
|
-
|
|
437
|
-
expect(result.code).toBe(0);
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
test('data 中有规则外的字段应忽略', () => {
|
|
441
|
-
const data = { name: 'john', extra: 'value' };
|
|
442
|
-
const rules = { name: { name: '姓名', type: 'string' } };
|
|
443
|
-
|
|
444
|
-
const result = validator.validate(data, rules);
|
|
445
|
-
|
|
446
|
-
expect(result.code).toBe(0);
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
test('规则中有 data 外的字段应跳过', () => {
|
|
450
|
-
const data = { name: 'john' };
|
|
451
|
-
const rules = {
|
|
452
|
-
name: { name: '姓名', type: 'string' },
|
|
453
|
-
email: { name: '邮箱', type: 'string' }
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
const result = validator.validate(data, rules);
|
|
457
|
-
|
|
458
|
-
// 非必填字段不存在应该通过
|
|
459
|
-
expect(result.code).toBe(0);
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
test('极端长度的字符串', () => {
|
|
463
|
-
const longString = 'a'.repeat(10000);
|
|
464
|
-
const data = { content: longString };
|
|
465
|
-
const rules = { content: { name: '内容', type: 'string', min: 0, max: 20000 } };
|
|
466
|
-
|
|
467
|
-
const result = validator.validate(data, rules);
|
|
468
|
-
|
|
469
|
-
expect(result.code).toBe(0);
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
test('极大的数字', () => {
|
|
473
|
-
const data = { value: Number.MAX_SAFE_INTEGER };
|
|
474
|
-
const rules = { value: { name: '值', type: 'number' } };
|
|
475
|
-
|
|
476
|
-
const result = validator.validate(data, rules);
|
|
477
|
-
|
|
478
|
-
expect(result.code).toBe(0);
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
test('极小的数字', () => {
|
|
482
|
-
const data = { value: Number.MIN_SAFE_INTEGER };
|
|
483
|
-
const rules = { value: { name: '值', type: 'number' } };
|
|
484
|
-
|
|
485
|
-
const result = validator.validate(data, rules);
|
|
486
|
-
|
|
487
|
-
expect(result.code).toBe(0);
|
|
488
|
-
});
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
describe('Validator - 代码逻辑问题分析', () => {
|
|
492
|
-
test('问题1:类型验证不严格', () => {
|
|
493
|
-
// **问题**:当前只检查 typeof value === 'number'
|
|
494
|
-
// 但 NaN 和 Infinity 也是 number 类型
|
|
495
|
-
|
|
496
|
-
const mockValidate = (value: any, type: string) => {
|
|
497
|
-
if (type === 'number') {
|
|
498
|
-
if (typeof value !== 'number' || isNaN(value) || !isFinite(value)) {
|
|
499
|
-
return '必须是有效的数字';
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
return null;
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
expect(mockValidate(123, 'number')).toBeNull();
|
|
506
|
-
expect(mockValidate(NaN, 'number')).toBe('必须是有效的数字');
|
|
507
|
-
expect(mockValidate(Infinity, 'number')).toBe('必须是有效的数字');
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
test('问题2:正则表达式没有缓存', () => {
|
|
511
|
-
// **问题**:每次验证都要 new RegExp()
|
|
512
|
-
// **建议**:缓存编译后的正则对象
|
|
513
|
-
|
|
514
|
-
const regexCache = new Map<string, RegExp>();
|
|
515
|
-
|
|
516
|
-
const mockGetRegex = (pattern: string) => {
|
|
517
|
-
if (regexCache.has(pattern)) {
|
|
518
|
-
return regexCache.get(pattern)!;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
const regex = new RegExp(pattern);
|
|
522
|
-
regexCache.set(pattern, regex);
|
|
523
|
-
return regex;
|
|
524
|
-
};
|
|
525
|
-
|
|
526
|
-
// 第一次编译
|
|
527
|
-
const t1 = performance.now();
|
|
528
|
-
mockGetRegex('^\\d+$');
|
|
529
|
-
const time1 = performance.now() - t1;
|
|
530
|
-
|
|
531
|
-
// 第二次应该更快
|
|
532
|
-
const t2 = performance.now();
|
|
533
|
-
mockGetRegex('^\\d+$');
|
|
534
|
-
const time2 = performance.now() - t2;
|
|
535
|
-
|
|
536
|
-
console.log(`首次编译: ${time1}ms, 缓存命中: ${time2}ms`);
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
test('问题3:错误消息不够详细', () => {
|
|
540
|
-
// **问题**:错误消息可能不够具体
|
|
541
|
-
// **建议**:提供更详细的错误信息
|
|
542
|
-
|
|
543
|
-
const mockValidateLength = (value: string, min: number, max: number, fieldName: string) => {
|
|
544
|
-
if (value.length < min) {
|
|
545
|
-
return `${fieldName} 长度不能少于 ${min} 个字符(当前 ${value.length} 个)`;
|
|
546
|
-
}
|
|
547
|
-
if (value.length > max) {
|
|
548
|
-
return `${fieldName} 长度不能超过 ${max} 个字符(当前 ${value.length} 个)`;
|
|
549
|
-
}
|
|
550
|
-
return null;
|
|
551
|
-
};
|
|
552
|
-
|
|
553
|
-
const error = mockValidateLength('a', 2, 10, '用户名');
|
|
554
|
-
expect(error).toBe('用户名 长度不能少于 2 个字符(当前 1 个)');
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
test('问题4:没有支持数组类型验证', () => {
|
|
558
|
-
// **问题**:当前只支持 string 和 number
|
|
559
|
-
// **建议**:支持 array、object、boolean 等类型
|
|
560
|
-
|
|
561
|
-
const mockValidateType = (value: any, type: string) => {
|
|
562
|
-
switch (type) {
|
|
563
|
-
case 'string':
|
|
564
|
-
return typeof value === 'string';
|
|
565
|
-
case 'number':
|
|
566
|
-
return typeof value === 'number' && isFinite(value);
|
|
567
|
-
case 'boolean':
|
|
568
|
-
return typeof value === 'boolean';
|
|
569
|
-
case 'array':
|
|
570
|
-
return Array.isArray(value);
|
|
571
|
-
case 'object':
|
|
572
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
573
|
-
default:
|
|
574
|
-
return false;
|
|
575
|
-
}
|
|
576
|
-
};
|
|
577
|
-
|
|
578
|
-
expect(mockValidateType('test', 'string')).toBe(true);
|
|
579
|
-
expect(mockValidateType(123, 'number')).toBe(true);
|
|
580
|
-
expect(mockValidateType(true, 'boolean')).toBe(true);
|
|
581
|
-
expect(mockValidateType([1, 2], 'array')).toBe(true);
|
|
582
|
-
expect(mockValidateType({ a: 1 }, 'object')).toBe(true);
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
test('问题5:没有支持自定义验证函数', () => {
|
|
586
|
-
// **问题**:某些复杂验证(如密码强度)无法用正则表达式完成
|
|
587
|
-
// **建议**:支持自定义验证函数
|
|
588
|
-
|
|
589
|
-
interface RuleWithValidator {
|
|
590
|
-
name: string;
|
|
591
|
-
type: string;
|
|
592
|
-
validator?: (value: any) => string | null;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
const mockValidateWithCustom = (value: any, rule: RuleWithValidator) => {
|
|
596
|
-
// 先执行类型检查
|
|
597
|
-
if (rule.type === 'string' && typeof value !== 'string') {
|
|
598
|
-
return '必须是字符串';
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// 再执行自定义验证
|
|
602
|
-
if (rule.validator) {
|
|
603
|
-
return rule.validator(value);
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
return null;
|
|
607
|
-
};
|
|
608
|
-
|
|
609
|
-
// 密码强度验证
|
|
610
|
-
const passwordRule: RuleWithValidator = {
|
|
611
|
-
name: '密码',
|
|
612
|
-
type: 'string',
|
|
613
|
-
validator: (value: string) => {
|
|
614
|
-
if (!/[A-Z]/.test(value)) return '必须包含大写字母';
|
|
615
|
-
if (!/[a-z]/.test(value)) return '必须包含小写字母';
|
|
616
|
-
if (!/\d/.test(value)) return '必须包含数字';
|
|
617
|
-
if (value.length < 8) return '长度不能少于 8 位';
|
|
618
|
-
return null;
|
|
619
|
-
}
|
|
620
|
-
};
|
|
621
|
-
|
|
622
|
-
expect(mockValidateWithCustom('weak', passwordRule)).toBe('必须包含大写字母');
|
|
623
|
-
expect(mockValidateWithCustom('Strong123', passwordRule)).toBeNull();
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
test('问题6:required 检查和类型验证分离导致重复', () => {
|
|
627
|
-
// **问题**:required 检查后,类型验证还要再检查一次空值
|
|
628
|
-
// **建议**:优化验证流程
|
|
629
|
-
|
|
630
|
-
const mockValidate = (value: any, isRequired: boolean, type: string) => {
|
|
631
|
-
// 第一步:必填检查
|
|
632
|
-
if (isRequired && (value === null || value === undefined || value === '')) {
|
|
633
|
-
return '必填项不能为空';
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// 第二步:如果值为空且非必填,跳过后续验证
|
|
637
|
-
if (!isRequired && (value === null || value === undefined || value === '')) {
|
|
638
|
-
return null; // 允许为空
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// 第三步:类型验证
|
|
642
|
-
if (type === 'number' && typeof value !== 'number') {
|
|
643
|
-
return '必须是数字';
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
return null;
|
|
647
|
-
};
|
|
648
|
-
|
|
649
|
-
expect(mockValidate(null, true, 'number')).toBe('必填项不能为空');
|
|
650
|
-
expect(mockValidate(null, false, 'number')).toBeNull(); // 非必填允许为空
|
|
651
|
-
expect(mockValidate('abc', false, 'number')).toBe('必须是数字');
|
|
652
|
-
});
|
|
653
|
-
});
|
package/types/addon.d.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Addon 配置类型定义
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Addon 作者信息
|
|
7
|
-
*/
|
|
8
|
-
export interface AddonAuthor {
|
|
9
|
-
/** 作者名称 */
|
|
10
|
-
name: string;
|
|
11
|
-
/** 作者邮箱 */
|
|
12
|
-
email?: string;
|
|
13
|
-
/** 作者网站 */
|
|
14
|
-
url?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Addon 配置
|
|
19
|
-
*/
|
|
20
|
-
export interface AddonConfig {
|
|
21
|
-
/** Addon 唯一标识(小写、短横线或下划线),例如 "admin"、"demo" */
|
|
22
|
-
name: string;
|
|
23
|
-
|
|
24
|
-
/** Addon 人类可读名称,例如 "管理后台" */
|
|
25
|
-
title: string;
|
|
26
|
-
|
|
27
|
-
/** 版本号(语义化版本),例如 "1.0.0" */
|
|
28
|
-
version?: string;
|
|
29
|
-
|
|
30
|
-
/** 简短描述 */
|
|
31
|
-
description?: string;
|
|
32
|
-
|
|
33
|
-
/** 作者信息 */
|
|
34
|
-
author?: AddonAuthor | string;
|
|
35
|
-
|
|
36
|
-
/** 源码仓库链接 */
|
|
37
|
-
repo?: string;
|
|
38
|
-
|
|
39
|
-
/** 关键词(用于搜索和分类) */
|
|
40
|
-
keywords?: string[];
|
|
41
|
-
|
|
42
|
-
/** 主入口文件路径(相对于 addon 目录),例如 "index.ts" */
|
|
43
|
-
entry?: string;
|
|
44
|
-
|
|
45
|
-
/** 依赖的其他 addon 或核心包 */
|
|
46
|
-
dependencies?: Record<string, string>;
|
|
47
|
-
|
|
48
|
-
/** 许可证 */
|
|
49
|
-
license?: string;
|
|
50
|
-
}
|