befly 3.8.29 → 3.8.31
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 +91 -6
- package/checks/checkApi.ts +2 -1
- package/checks/checkApp.ts +31 -1
- package/checks/checkTable.ts +3 -2
- package/hooks/cors.ts +3 -3
- package/hooks/parser.ts +8 -6
- package/hooks/permission.ts +12 -5
- package/hooks/validator.ts +1 -1
- package/lib/cacheHelper.ts +73 -65
- package/lib/cipher.ts +2 -1
- package/lib/connect.ts +23 -52
- package/lib/dbHelper.ts +14 -11
- package/lib/jwt.ts +58 -437
- package/lib/logger.ts +76 -197
- package/lib/redisHelper.ts +163 -1
- package/lib/sqlBuilder.ts +2 -1
- package/lib/validator.ts +150 -384
- package/loader/loadApis.ts +4 -7
- package/loader/loadHooks.ts +6 -5
- package/loader/loadPlugins.ts +11 -13
- package/main.ts +26 -53
- package/package.json +10 -8
- package/paths.ts +0 -6
- package/plugins/cipher.ts +1 -1
- package/plugins/config.ts +3 -4
- package/plugins/db.ts +6 -7
- package/plugins/jwt.ts +7 -6
- package/plugins/logger.ts +6 -6
- package/plugins/redis.ts +9 -13
- package/router/api.ts +2 -2
- package/router/static.ts +4 -8
- package/sync/syncAll.ts +8 -13
- package/sync/syncApi.ts +14 -10
- package/sync/syncDb/apply.ts +1 -2
- package/sync/syncDb.ts +12 -15
- package/sync/syncDev.ts +19 -56
- package/sync/syncMenu.ts +182 -137
- package/tests/cacheHelper.test.ts +327 -0
- package/tests/dbHelper-columns.test.ts +5 -20
- package/tests/dbHelper-execute.test.ts +14 -68
- package/tests/fields-redis-cache.test.ts +5 -3
- package/tests/integration.test.ts +17 -32
- package/tests/jwt.test.ts +36 -94
- package/tests/logger.test.ts +32 -34
- package/tests/redisHelper.test.ts +271 -2
- package/tests/redisKeys.test.ts +76 -0
- package/tests/sync-connection.test.ts +0 -6
- package/tests/syncDb-constants.test.ts +12 -12
- package/tests/util.test.ts +5 -1
- package/tests/validator.test.ts +611 -85
- package/types/befly.d.ts +9 -15
- package/types/cache.d.ts +73 -0
- package/types/common.d.ts +10 -128
- package/types/database.d.ts +221 -5
- package/types/index.ts +6 -5
- package/types/plugin.d.ts +1 -4
- package/types/redis.d.ts +37 -2
- package/types/table.d.ts +175 -0
- package/config.ts +0 -70
- package/hooks/_rateLimit.ts +0 -64
- package/lib/regexAliases.ts +0 -59
- package/lib/xml.ts +0 -383
- package/tests/validator-advanced.test.ts +0 -653
- package/tests/xml.test.ts +0 -101
- 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 -43
- package/types/tool.d.ts +0 -67
- package/types/validator.d.ts +0 -43
package/tests/validator.test.ts
CHANGED
|
@@ -1,148 +1,674 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Validator 验证器测试
|
|
3
|
+
* 测试静态类方法、返回结构、各种验证场景
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
7
|
import { Validator } from '../lib/validator';
|
|
3
8
|
|
|
4
|
-
|
|
9
|
+
// ========== 返回结构测试 ==========
|
|
10
|
+
|
|
11
|
+
describe('Validator.validate - 返回结构', () => {
|
|
12
|
+
test('验证通过时返回正确结构', () => {
|
|
13
|
+
const data = { name: 'test' };
|
|
14
|
+
const rules = { name: { name: '名称', type: 'string', min: 2, max: 10 } };
|
|
15
|
+
|
|
16
|
+
const result = Validator.validate(data, rules);
|
|
5
17
|
|
|
6
|
-
describe('Validator - 基本验证', () => {
|
|
7
|
-
test('验证通过', () => {
|
|
8
|
-
const data = { username: 'john' };
|
|
9
|
-
const rules = { username: { name: '用户名', type: 'string', min: 2, max: 20 } };
|
|
10
|
-
const result = validator.validate(data, rules);
|
|
11
18
|
expect(result.code).toBe(0);
|
|
12
|
-
expect(
|
|
19
|
+
expect(result.failed).toBe(false);
|
|
20
|
+
expect(result.firstError).toBeNull();
|
|
21
|
+
expect(result.errors).toEqual([]);
|
|
22
|
+
expect(result.errorFields).toEqual([]);
|
|
23
|
+
expect(result.fieldErrors).toEqual({});
|
|
13
24
|
});
|
|
14
25
|
|
|
15
|
-
test('
|
|
16
|
-
const data = {};
|
|
17
|
-
const rules = {
|
|
18
|
-
|
|
19
|
-
const result =
|
|
26
|
+
test('验证失败时返回正确结构', () => {
|
|
27
|
+
const data = { name: 'a' };
|
|
28
|
+
const rules = { name: { name: '名称', type: 'string', min: 2, max: 10 } };
|
|
29
|
+
|
|
30
|
+
const result = Validator.validate(data, rules);
|
|
31
|
+
|
|
20
32
|
expect(result.code).toBe(1);
|
|
21
|
-
expect(result.
|
|
33
|
+
expect(result.failed).toBe(true);
|
|
34
|
+
expect(result.firstError).toBeDefined();
|
|
35
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
36
|
+
expect(result.errorFields).toContain('name');
|
|
37
|
+
expect(result.fieldErrors.name).toBeDefined();
|
|
22
38
|
});
|
|
23
39
|
|
|
24
|
-
test('
|
|
25
|
-
const data = {
|
|
26
|
-
const rules = {
|
|
27
|
-
|
|
28
|
-
|
|
40
|
+
test('多字段错误时返回所有错误', () => {
|
|
41
|
+
const data = { name: 'a', age: 200 };
|
|
42
|
+
const rules = {
|
|
43
|
+
name: { name: '名称', type: 'string', min: 2, max: 10 },
|
|
44
|
+
age: { name: '年龄', type: 'number', min: 0, max: 150 }
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const result = Validator.validate(data, rules);
|
|
48
|
+
|
|
29
49
|
expect(result.code).toBe(1);
|
|
30
|
-
expect(result.
|
|
50
|
+
expect(result.errors.length).toBe(2);
|
|
51
|
+
expect(result.errorFields.length).toBe(2);
|
|
52
|
+
expect(result.errorFields).toContain('name');
|
|
53
|
+
expect(result.errorFields).toContain('age');
|
|
31
54
|
});
|
|
32
55
|
});
|
|
33
56
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
57
|
+
// ========== 参数检查测试 ==========
|
|
58
|
+
|
|
59
|
+
describe('Validator.validate - 参数检查', () => {
|
|
60
|
+
test('data 为 null', () => {
|
|
61
|
+
const result = Validator.validate(null as any, {});
|
|
62
|
+
|
|
63
|
+
expect(result.code).toBe(1);
|
|
64
|
+
expect(result.firstError).toContain('对象格式');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('data 为 undefined', () => {
|
|
68
|
+
const result = Validator.validate(undefined as any, {});
|
|
69
|
+
|
|
70
|
+
expect(result.code).toBe(1);
|
|
71
|
+
expect(result.firstError).toContain('对象格式');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('data 为数组', () => {
|
|
75
|
+
const result = Validator.validate([] as any, {});
|
|
76
|
+
|
|
77
|
+
expect(result.code).toBe(1);
|
|
78
|
+
expect(result.firstError).toContain('对象格式');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('data 为字符串', () => {
|
|
82
|
+
const result = Validator.validate('string' as any, {});
|
|
83
|
+
|
|
84
|
+
expect(result.code).toBe(1);
|
|
85
|
+
expect(result.firstError).toContain('对象格式');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('rules 为 null', () => {
|
|
89
|
+
const result = Validator.validate({}, null as any);
|
|
90
|
+
|
|
91
|
+
expect(result.code).toBe(1);
|
|
92
|
+
expect(result.firstError).toContain('对象格式');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('rules 为 undefined', () => {
|
|
96
|
+
const result = Validator.validate({}, undefined as any);
|
|
97
|
+
|
|
98
|
+
expect(result.code).toBe(1);
|
|
99
|
+
expect(result.firstError).toContain('对象格式');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// ========== 必填字段测试 ==========
|
|
104
|
+
|
|
105
|
+
describe('Validator.validate - 必填字段', () => {
|
|
106
|
+
const rules = { name: { name: '名称', type: 'string', min: 2, max: 20 } };
|
|
107
|
+
|
|
108
|
+
test('字段存在且有值 - 通过', () => {
|
|
109
|
+
const result = Validator.validate({ name: 'test' }, rules, ['name']);
|
|
110
|
+
expect(result.code).toBe(0);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('字段不存在 - 失败', () => {
|
|
114
|
+
const result = Validator.validate({}, rules, ['name']);
|
|
115
|
+
expect(result.code).toBe(1);
|
|
116
|
+
expect(result.fieldErrors.name).toContain('必填项');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('字段值为空字符串 - 失败', () => {
|
|
120
|
+
const result = Validator.validate({ name: '' }, rules, ['name']);
|
|
121
|
+
expect(result.code).toBe(1);
|
|
122
|
+
expect(result.fieldErrors.name).toContain('必填项');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('字段值为 null - 失败', () => {
|
|
126
|
+
const result = Validator.validate({ name: null }, rules, ['name']);
|
|
127
|
+
expect(result.code).toBe(1);
|
|
128
|
+
expect(result.fieldErrors.name).toContain('必填项');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('字段值为 undefined - 失败', () => {
|
|
132
|
+
const result = Validator.validate({ name: undefined }, rules, ['name']);
|
|
133
|
+
expect(result.code).toBe(1);
|
|
134
|
+
expect(result.fieldErrors.name).toContain('必填项');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('多个必填字段全部缺失', () => {
|
|
138
|
+
const multiRules = {
|
|
139
|
+
name: { name: '名称', type: 'string', min: 2, max: 20 },
|
|
140
|
+
email: { name: '邮箱', type: 'string', regexp: '@email' }
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const result = Validator.validate({}, multiRules, ['name', 'email']);
|
|
144
|
+
|
|
145
|
+
expect(result.code).toBe(1);
|
|
146
|
+
expect(result.errors.length).toBe(2);
|
|
147
|
+
expect(result.fieldErrors.name).toContain('必填项');
|
|
148
|
+
expect(result.fieldErrors.email).toContain('必填项');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('必填字段使用字段标签', () => {
|
|
152
|
+
const result = Validator.validate({}, rules, ['name']);
|
|
153
|
+
expect(result.firstError).toContain('名称');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('必填字段无标签时使用字段名', () => {
|
|
157
|
+
const noLabelRules = { name: { type: 'string', min: 2, max: 20 } };
|
|
158
|
+
const result = Validator.validate({}, noLabelRules, ['name']);
|
|
159
|
+
expect(result.firstError).toContain('name');
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// ========== string 类型测试 ==========
|
|
164
|
+
|
|
165
|
+
describe('Validator.validate - string 类型', () => {
|
|
166
|
+
test('有效字符串 - 通过', () => {
|
|
37
167
|
const rules = { name: { name: '名称', type: 'string', min: 2, max: 10 } };
|
|
38
|
-
const result =
|
|
168
|
+
const result = Validator.validate({ name: 'test' }, rules);
|
|
39
169
|
expect(result.code).toBe(0);
|
|
40
170
|
});
|
|
41
171
|
|
|
42
|
-
test('
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
172
|
+
test('非字符串类型 - 失败', () => {
|
|
173
|
+
const rules = { name: { name: '名称', type: 'string', min: 2, max: 10 } };
|
|
174
|
+
const result = Validator.validate({ name: 123 }, rules);
|
|
175
|
+
expect(result.code).toBe(1);
|
|
176
|
+
expect(result.firstError).toContain('字符串');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('长度小于 min - 失败', () => {
|
|
180
|
+
const rules = { name: { name: '名称', type: 'string', min: 5, max: 20 } };
|
|
181
|
+
const result = Validator.validate({ name: 'abc' }, rules);
|
|
182
|
+
expect(result.code).toBe(1);
|
|
183
|
+
expect(result.firstError).toContain('5');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('长度等于 min - 通过', () => {
|
|
187
|
+
const rules = { name: { name: '名称', type: 'string', min: 3, max: 20 } };
|
|
188
|
+
const result = Validator.validate({ name: 'abc' }, rules);
|
|
46
189
|
expect(result.code).toBe(0);
|
|
47
190
|
});
|
|
48
191
|
|
|
49
|
-
test('
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
|
|
192
|
+
test('长度大于 max - 失败', () => {
|
|
193
|
+
const rules = { name: { name: '名称', type: 'string', min: 2, max: 5 } };
|
|
194
|
+
const result = Validator.validate({ name: 'abcdefg' }, rules);
|
|
195
|
+
expect(result.code).toBe(1);
|
|
196
|
+
expect(result.firstError).toContain('5');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('长度等于 max - 通过', () => {
|
|
200
|
+
const rules = { name: { name: '名称', type: 'string', min: 2, max: 5 } };
|
|
201
|
+
const result = Validator.validate({ name: 'abcde' }, rules);
|
|
53
202
|
expect(result.code).toBe(0);
|
|
54
203
|
});
|
|
55
204
|
|
|
56
|
-
test('
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
205
|
+
test('中文字符按字符数计算', () => {
|
|
206
|
+
const rules = { name: { name: '名称', type: 'string', min: 2, max: 5 } };
|
|
207
|
+
const result = Validator.validate({ name: '你好世界' }, rules); // 4 个字符
|
|
208
|
+
expect(result.code).toBe(0);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('max 为 0 时不检查最大长度', () => {
|
|
212
|
+
const rules = { name: { name: '名称', type: 'string', min: 0, max: 0 } };
|
|
213
|
+
const result = Validator.validate({ name: 'a'.repeat(1000) }, rules);
|
|
60
214
|
expect(result.code).toBe(0);
|
|
61
215
|
});
|
|
62
216
|
});
|
|
63
217
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
218
|
+
// ========== text 类型测试 ==========
|
|
219
|
+
|
|
220
|
+
describe('Validator.validate - text 类型', () => {
|
|
221
|
+
test('text 类型与 string 类型行为一致', () => {
|
|
222
|
+
const rules = { content: { name: '内容', type: 'text', min: 2, max: 100 } };
|
|
223
|
+
const result = Validator.validate({ content: 'hello world' }, rules);
|
|
224
|
+
expect(result.code).toBe(0);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test('text 类型非字符串 - 失败', () => {
|
|
228
|
+
const rules = { content: { name: '内容', type: 'text', min: 2, max: 100 } };
|
|
229
|
+
const result = Validator.validate({ content: 123 }, rules);
|
|
69
230
|
expect(result.code).toBe(1);
|
|
70
|
-
expect(result.fields.name).toBeDefined();
|
|
71
231
|
});
|
|
232
|
+
});
|
|
72
233
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
234
|
+
// ========== number 类型测试 ==========
|
|
235
|
+
|
|
236
|
+
describe('Validator.validate - number 类型', () => {
|
|
237
|
+
test('有效数字 - 通过', () => {
|
|
238
|
+
const rules = { age: { name: '年龄', type: 'number', min: 0, max: 150 } };
|
|
239
|
+
const result = Validator.validate({ age: 25 }, rules);
|
|
240
|
+
expect(result.code).toBe(0);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('字符串数字自动转换 - 通过', () => {
|
|
244
|
+
const rules = { age: { name: '年龄', type: 'number', min: 0, max: 150 } };
|
|
245
|
+
const result = Validator.validate({ age: '25' }, rules);
|
|
246
|
+
expect(result.code).toBe(0);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test('非数字字符串 - 失败', () => {
|
|
250
|
+
const rules = { age: { name: '年龄', type: 'number', min: 0, max: 150 } };
|
|
251
|
+
const result = Validator.validate({ age: 'abc' }, rules);
|
|
77
252
|
expect(result.code).toBe(1);
|
|
78
|
-
expect(result.
|
|
253
|
+
expect(result.firstError).toContain('数字');
|
|
79
254
|
});
|
|
80
255
|
|
|
81
|
-
test('
|
|
82
|
-
const data = { age: 200 };
|
|
256
|
+
test('NaN - 失败', () => {
|
|
83
257
|
const rules = { age: { name: '年龄', type: 'number', min: 0, max: 150 } };
|
|
84
|
-
const result =
|
|
258
|
+
const result = Validator.validate({ age: NaN }, rules);
|
|
259
|
+
expect(result.code).toBe(1);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('Infinity - 失败', () => {
|
|
263
|
+
const rules = { value: { name: '值', type: 'number', min: 0, max: 100 } };
|
|
264
|
+
const result = Validator.validate({ value: Infinity }, rules);
|
|
85
265
|
expect(result.code).toBe(1);
|
|
86
|
-
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('-Infinity - 失败', () => {
|
|
269
|
+
const rules = { value: { name: '值', type: 'number', min: 0, max: 100 } };
|
|
270
|
+
const result = Validator.validate({ value: -Infinity }, rules);
|
|
271
|
+
expect(result.code).toBe(1);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('0 是有效值', () => {
|
|
275
|
+
const rules = { count: { name: '计数', type: 'number', min: 0, max: 100 } };
|
|
276
|
+
const result = Validator.validate({ count: 0 }, rules);
|
|
277
|
+
expect(result.code).toBe(0);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('负数范围验证', () => {
|
|
281
|
+
const rules = { temp: { name: '温度', type: 'number', min: -50, max: 50 } };
|
|
282
|
+
const result = Validator.validate({ temp: -30 }, rules);
|
|
283
|
+
expect(result.code).toBe(0);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test('小于 min - 失败', () => {
|
|
287
|
+
const rules = { age: { name: '年龄', type: 'number', min: 18, max: 100 } };
|
|
288
|
+
const result = Validator.validate({ age: 10 }, rules);
|
|
289
|
+
expect(result.code).toBe(1);
|
|
290
|
+
expect(result.firstError).toContain('18');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test('等于 min - 通过', () => {
|
|
294
|
+
const rules = { age: { name: '年龄', type: 'number', min: 18, max: 100 } };
|
|
295
|
+
const result = Validator.validate({ age: 18 }, rules);
|
|
296
|
+
expect(result.code).toBe(0);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test('大于 max - 失败', () => {
|
|
300
|
+
const rules = { age: { name: '年龄', type: 'number', min: 0, max: 100 } };
|
|
301
|
+
const result = Validator.validate({ age: 150 }, rules);
|
|
302
|
+
expect(result.code).toBe(1);
|
|
303
|
+
expect(result.firstError).toContain('100');
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test('等于 max - 通过', () => {
|
|
307
|
+
const rules = { age: { name: '年龄', type: 'number', min: 0, max: 100 } };
|
|
308
|
+
const result = Validator.validate({ age: 100 }, rules);
|
|
309
|
+
expect(result.code).toBe(0);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test('浮点数', () => {
|
|
313
|
+
const rules = { price: { name: '价格', type: 'number', min: 0, max: 1000 } };
|
|
314
|
+
const result = Validator.validate({ price: 99.99 }, rules);
|
|
315
|
+
expect(result.code).toBe(0);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test('max 为 0 时不检查最大值', () => {
|
|
319
|
+
const rules = { value: { name: '值', type: 'number', min: 0, max: 0 } };
|
|
320
|
+
const result = Validator.validate({ value: 999999 }, rules);
|
|
321
|
+
expect(result.code).toBe(0);
|
|
87
322
|
});
|
|
88
323
|
});
|
|
89
324
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
325
|
+
// ========== array_string 类型测试 ==========
|
|
326
|
+
|
|
327
|
+
describe('Validator.validate - array_string 类型', () => {
|
|
328
|
+
test('有效数组 - 通过', () => {
|
|
329
|
+
const rules = { tags: { name: '标签', type: 'array_string', min: 1, max: 5 } };
|
|
330
|
+
const result = Validator.validate({ tags: ['a', 'b', 'c'] }, rules);
|
|
95
331
|
expect(result.code).toBe(0);
|
|
96
332
|
});
|
|
97
333
|
|
|
98
|
-
test('
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
|
|
334
|
+
test('非数组类型 - 失败', () => {
|
|
335
|
+
const rules = { tags: { name: '标签', type: 'array_string', min: 1, max: 5 } };
|
|
336
|
+
const result = Validator.validate({ tags: 'not array' }, rules);
|
|
337
|
+
expect(result.code).toBe(1);
|
|
338
|
+
expect(result.firstError).toContain('数组');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test('元素数量小于 min - 失败', () => {
|
|
342
|
+
const rules = { tags: { name: '标签', type: 'array_string', min: 3, max: 10 } };
|
|
343
|
+
const result = Validator.validate({ tags: ['a', 'b'] }, rules);
|
|
102
344
|
expect(result.code).toBe(1);
|
|
103
|
-
expect(result.
|
|
345
|
+
expect(result.firstError).toContain('3');
|
|
104
346
|
});
|
|
105
347
|
|
|
106
|
-
test('
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
const result = validator.validate(data, rules);
|
|
348
|
+
test('元素数量等于 min - 通过', () => {
|
|
349
|
+
const rules = { tags: { name: '标签', type: 'array_string', min: 2, max: 10 } };
|
|
350
|
+
const result = Validator.validate({ tags: ['a', 'b'] }, rules);
|
|
110
351
|
expect(result.code).toBe(0);
|
|
111
352
|
});
|
|
112
353
|
|
|
113
|
-
test('
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const result = validator.validate(data, rules);
|
|
354
|
+
test('元素数量大于 max - 失败', () => {
|
|
355
|
+
const rules = { tags: { name: '标签', type: 'array_string', min: 1, max: 3 } };
|
|
356
|
+
const result = Validator.validate({ tags: ['a', 'b', 'c', 'd', 'e'] }, rules);
|
|
117
357
|
expect(result.code).toBe(1);
|
|
118
|
-
expect(result.
|
|
358
|
+
expect(result.firstError).toContain('3');
|
|
119
359
|
});
|
|
120
360
|
|
|
121
|
-
test('
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
|
|
361
|
+
test('元素数量等于 max - 通过', () => {
|
|
362
|
+
const rules = { tags: { name: '标签', type: 'array_string', min: 1, max: 3 } };
|
|
363
|
+
const result = Validator.validate({ tags: ['a', 'b', 'c'] }, rules);
|
|
364
|
+
expect(result.code).toBe(0);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test('空数组', () => {
|
|
368
|
+
const rules = { tags: { name: '标签', type: 'array_string', min: 0, max: 10 } };
|
|
369
|
+
const result = Validator.validate({ tags: [] }, rules);
|
|
370
|
+
expect(result.code).toBe(0);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test('数组元素正则验证 - 全部通过', () => {
|
|
374
|
+
const rules = { emails: { name: '邮箱列表', type: 'array_string', min: 1, max: 10, regexp: '@email' } };
|
|
375
|
+
const result = Validator.validate({ emails: ['a@test.com', 'b@test.com'] }, rules);
|
|
376
|
+
expect(result.code).toBe(0);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test('数组元素正则验证 - 部分失败', () => {
|
|
380
|
+
const rules = { emails: { name: '邮箱列表', type: 'array_string', min: 1, max: 10, regexp: '@email' } };
|
|
381
|
+
const result = Validator.validate({ emails: ['a@test.com', 'invalid'] }, rules);
|
|
125
382
|
expect(result.code).toBe(1);
|
|
126
|
-
expect(result.
|
|
383
|
+
expect(result.firstError).toContain('格式');
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// ========== array_text 类型测试 ==========
|
|
388
|
+
|
|
389
|
+
describe('Validator.validate - array_text 类型', () => {
|
|
390
|
+
test('array_text 与 array_string 行为一致', () => {
|
|
391
|
+
const rules = { items: { name: '条目', type: 'array_text', min: 1, max: 5 } };
|
|
392
|
+
const result = Validator.validate({ items: ['item1', 'item2'] }, rules);
|
|
393
|
+
expect(result.code).toBe(0);
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// ========== 正则验证测试 ==========
|
|
398
|
+
|
|
399
|
+
describe('Validator.validate - 正则别名', () => {
|
|
400
|
+
test('@email - 有效邮箱', () => {
|
|
401
|
+
const validEmails = ['test@example.com', 'user.name@domain.co.uk', 'admin+tag@site.org', 'test123@test.cn'];
|
|
402
|
+
|
|
403
|
+
validEmails.forEach((email) => {
|
|
404
|
+
const rules = { email: { name: '邮箱', type: 'string', regexp: '@email' } };
|
|
405
|
+
const result = Validator.validate({ email: email }, rules);
|
|
406
|
+
expect(result.code).toBe(0);
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test('@email - 无效邮箱', () => {
|
|
411
|
+
const invalidEmails = ['plaintext', '@example.com', 'user@', 'user @domain.com'];
|
|
412
|
+
|
|
413
|
+
invalidEmails.forEach((email) => {
|
|
414
|
+
const rules = { email: { name: '邮箱', type: 'string', regexp: '@email' } };
|
|
415
|
+
const result = Validator.validate({ email: email }, rules);
|
|
416
|
+
expect(result.code).toBe(1);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test('@phone - 有效手机号', () => {
|
|
421
|
+
const validPhones = ['13800138000', '15012345678', '18888888888', '19912345678'];
|
|
422
|
+
|
|
423
|
+
validPhones.forEach((phone) => {
|
|
424
|
+
const rules = { phone: { name: '手机号', type: 'string', regexp: '@phone' } };
|
|
425
|
+
const result = Validator.validate({ phone: phone }, rules);
|
|
426
|
+
expect(result.code).toBe(0);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test('@phone - 无效手机号', () => {
|
|
431
|
+
const invalidPhones = [
|
|
432
|
+
'12345678901', // 首位不是 1 开头的有效段
|
|
433
|
+
'1380013800', // 10 位
|
|
434
|
+
'138001380001', // 12 位
|
|
435
|
+
'abc'
|
|
436
|
+
];
|
|
437
|
+
|
|
438
|
+
invalidPhones.forEach((phone) => {
|
|
439
|
+
const rules = { phone: { name: '手机号', type: 'string', regexp: '@phone' } };
|
|
440
|
+
const result = Validator.validate({ phone: phone }, rules);
|
|
441
|
+
expect(result.code).toBe(1);
|
|
442
|
+
});
|
|
127
443
|
});
|
|
128
444
|
});
|
|
129
445
|
|
|
130
|
-
describe('Validator -
|
|
131
|
-
test('
|
|
132
|
-
const
|
|
446
|
+
describe('Validator.validate - 自定义正则', () => {
|
|
447
|
+
test('纯数字', () => {
|
|
448
|
+
const rules = { code: { name: '验证码', type: 'string', regexp: '^\\d+$' } };
|
|
449
|
+
|
|
450
|
+
expect(Validator.validate({ code: '123456' }, rules).code).toBe(0);
|
|
451
|
+
expect(Validator.validate({ code: '12a34' }, rules).code).toBe(1);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test('字母数字组合', () => {
|
|
455
|
+
const rules = { username: { name: '用户名', type: 'string', regexp: '^[a-zA-Z0-9]+$' } };
|
|
456
|
+
|
|
457
|
+
expect(Validator.validate({ username: 'user123' }, rules).code).toBe(0);
|
|
458
|
+
expect(Validator.validate({ username: 'user@123' }, rules).code).toBe(1);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
test('无效正则不抛出异常', () => {
|
|
462
|
+
const rules = { value: { name: '值', type: 'string', regexp: '[invalid(' } };
|
|
463
|
+
const result = Validator.validate({ value: 'test' }, rules);
|
|
464
|
+
// 无效正则应该导致验证失败,但不应抛出异常
|
|
133
465
|
expect(result.code).toBe(1);
|
|
134
|
-
expect(result.fields.error).toContain('对象格式');
|
|
135
466
|
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// ========== 非必填字段测试 ==========
|
|
470
|
+
|
|
471
|
+
describe('Validator.validate - 非必填字段', () => {
|
|
472
|
+
test('非必填字段不存在时不验证', () => {
|
|
473
|
+
const rules = {
|
|
474
|
+
name: { name: '名称', type: 'string', min: 2, max: 20 },
|
|
475
|
+
age: { name: '年龄', type: 'number', min: 0, max: 150 }
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// 只提供 name,不提供 age(age 不在 required 中)
|
|
479
|
+
const result = Validator.validate({ name: 'test' }, rules, ['name']);
|
|
480
|
+
|
|
481
|
+
expect(result.code).toBe(0);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
test('非必填字段存在时会验证', () => {
|
|
485
|
+
const rules = {
|
|
486
|
+
name: { name: '名称', type: 'string', min: 2, max: 20 },
|
|
487
|
+
age: { name: '年龄', type: 'number', min: 0, max: 150 }
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
// 提供了 age 但值无效
|
|
491
|
+
const result = Validator.validate({ name: 'test', age: 200 }, rules, ['name']);
|
|
136
492
|
|
|
137
|
-
test('rules 不是对象', () => {
|
|
138
|
-
const result = validator.validate({}, null as any);
|
|
139
493
|
expect(result.code).toBe(1);
|
|
140
|
-
expect(result.
|
|
494
|
+
expect(result.fieldErrors.age).toBeDefined();
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// ========== Validator.single 测试 ==========
|
|
499
|
+
|
|
500
|
+
describe('Validator.single - 单值验证', () => {
|
|
501
|
+
test('有效值返回转换后的值', () => {
|
|
502
|
+
const fieldDef = { name: '年龄', type: 'number', min: 0, max: 150 };
|
|
503
|
+
const result = Validator.single(25, fieldDef);
|
|
504
|
+
|
|
505
|
+
expect(result.error).toBeNull();
|
|
506
|
+
expect(result.value).toBe(25);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test('字符串数字自动转换', () => {
|
|
510
|
+
const fieldDef = { name: '年龄', type: 'number', min: 0, max: 150 };
|
|
511
|
+
const result = Validator.single('30', fieldDef);
|
|
512
|
+
|
|
513
|
+
expect(result.error).toBeNull();
|
|
514
|
+
expect(result.value).toBe(30);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test('无效值返回错误', () => {
|
|
518
|
+
const fieldDef = { name: '年龄', type: 'number', min: 0, max: 150 };
|
|
519
|
+
const result = Validator.single('abc', fieldDef);
|
|
520
|
+
|
|
521
|
+
expect(result.error).toBeDefined();
|
|
522
|
+
expect(result.value).toBeNull();
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
test('空值返回默认值 - number', () => {
|
|
526
|
+
const fieldDef = { name: '计数', type: 'number', default: null };
|
|
527
|
+
const result = Validator.single(null, fieldDef);
|
|
528
|
+
|
|
529
|
+
expect(result.error).toBeNull();
|
|
530
|
+
expect(result.value).toBe(0); // number 类型默认值
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
test('空值返回默认值 - string', () => {
|
|
534
|
+
const fieldDef = { name: '名称', type: 'string', default: null };
|
|
535
|
+
const result = Validator.single(undefined, fieldDef);
|
|
536
|
+
|
|
537
|
+
expect(result.error).toBeNull();
|
|
538
|
+
expect(result.value).toBe(''); // string 类型默认值
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
test('空值返回默认值 - array_string', () => {
|
|
542
|
+
const fieldDef = { name: '标签', type: 'array_string', default: null };
|
|
543
|
+
const result = Validator.single('', fieldDef);
|
|
544
|
+
|
|
545
|
+
expect(result.error).toBeNull();
|
|
546
|
+
expect(result.value).toEqual([]); // array 类型默认值
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
test('使用自定义默认值', () => {
|
|
550
|
+
const fieldDef = { name: '状态', type: 'number', default: 1 };
|
|
551
|
+
const result = Validator.single(null, fieldDef);
|
|
552
|
+
|
|
553
|
+
expect(result.error).toBeNull();
|
|
554
|
+
expect(result.value).toBe(1);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
test('字符串数字默认值转换', () => {
|
|
558
|
+
const fieldDef = { name: '计数', type: 'number', default: '100' };
|
|
559
|
+
const result = Validator.single(null, fieldDef);
|
|
560
|
+
|
|
561
|
+
expect(result.error).toBeNull();
|
|
562
|
+
expect(result.value).toBe(100);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
test('数组默认值解析', () => {
|
|
566
|
+
const fieldDef = { name: '标签', type: 'array_string', default: '["a","b"]' };
|
|
567
|
+
const result = Validator.single(null, fieldDef);
|
|
568
|
+
|
|
569
|
+
expect(result.error).toBeNull();
|
|
570
|
+
expect(result.value).toEqual(['a', 'b']);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
test('空数组默认值', () => {
|
|
574
|
+
const fieldDef = { name: '标签', type: 'array_string', default: '[]' };
|
|
575
|
+
const result = Validator.single(null, fieldDef);
|
|
576
|
+
|
|
577
|
+
expect(result.error).toBeNull();
|
|
578
|
+
expect(result.value).toEqual([]);
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
test('规则验证失败', () => {
|
|
582
|
+
const fieldDef = { name: '年龄', type: 'number', min: 18, max: 100 };
|
|
583
|
+
const result = Validator.single(10, fieldDef);
|
|
584
|
+
|
|
585
|
+
expect(result.error).toBeDefined();
|
|
586
|
+
expect(result.error).toContain('18');
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
test('正则验证失败', () => {
|
|
590
|
+
const fieldDef = { name: '邮箱', type: 'string', regexp: '@email' };
|
|
591
|
+
const result = Validator.single('invalid', fieldDef);
|
|
592
|
+
|
|
593
|
+
expect(result.error).toBeDefined();
|
|
594
|
+
expect(result.error).toContain('格式');
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
// ========== 错误消息测试 ==========
|
|
599
|
+
|
|
600
|
+
describe('Validator - 错误消息格式', () => {
|
|
601
|
+
test('错误消息包含字段标签', () => {
|
|
602
|
+
const rules = { age: { name: '年龄', type: 'number', min: 18, max: 100 } };
|
|
603
|
+
const result = Validator.validate({ age: 10 }, rules);
|
|
604
|
+
|
|
605
|
+
expect(result.firstError).toContain('年龄');
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
test('必填错误消息格式', () => {
|
|
609
|
+
const rules = { name: { name: '姓名', type: 'string', min: 2, max: 20 } };
|
|
610
|
+
const result = Validator.validate({}, rules, ['name']);
|
|
611
|
+
|
|
612
|
+
expect(result.firstError).toBe('姓名为必填项');
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
test('长度错误消息格式', () => {
|
|
616
|
+
const rules = { name: { name: '姓名', type: 'string', min: 5, max: 20 } };
|
|
617
|
+
const result = Validator.validate({ name: 'abc' }, rules);
|
|
618
|
+
|
|
619
|
+
expect(result.firstError).toContain('姓名');
|
|
620
|
+
expect(result.firstError).toContain('5');
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test('数值范围错误消息格式', () => {
|
|
624
|
+
const rules = { age: { name: '年龄', type: 'number', min: 18, max: 100 } };
|
|
625
|
+
const result = Validator.validate({ age: 10 }, rules);
|
|
626
|
+
|
|
627
|
+
expect(result.firstError).toContain('年龄');
|
|
628
|
+
expect(result.firstError).toContain('18');
|
|
141
629
|
});
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
// ========== 边界条件测试 ==========
|
|
142
633
|
|
|
143
|
-
|
|
144
|
-
|
|
634
|
+
describe('Validator - 边界条件', () => {
|
|
635
|
+
test('空 rules 对象', () => {
|
|
636
|
+
const result = Validator.validate({ name: 'test' }, {});
|
|
637
|
+
expect(result.code).toBe(0);
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
test('空 required 数组', () => {
|
|
641
|
+
const rules = { name: { name: '名称', type: 'string', min: 2, max: 20 } };
|
|
642
|
+
const result = Validator.validate({ name: 'test' }, rules, []);
|
|
643
|
+
expect(result.code).toBe(0);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
test('required 中的字段不在 rules 中', () => {
|
|
647
|
+
const rules = { name: { name: '名称', type: 'string', min: 2, max: 20 } };
|
|
648
|
+
const result = Validator.validate({}, rules, ['unknown']);
|
|
649
|
+
|
|
650
|
+
// 应该报错,显示 unknown 为必填项
|
|
145
651
|
expect(result.code).toBe(1);
|
|
146
|
-
expect(result.
|
|
652
|
+
expect(result.fieldErrors.unknown).toBeDefined();
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
test('data 中的字段不在 rules 中', () => {
|
|
656
|
+
const rules = { name: { name: '名称', type: 'string', min: 2, max: 20 } };
|
|
657
|
+
const result = Validator.validate({ name: 'test', extra: 'ignored' }, rules);
|
|
658
|
+
|
|
659
|
+
// extra 字段应该被忽略
|
|
660
|
+
expect(result.code).toBe(0);
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
test('min/max 为 null 时不检查', () => {
|
|
664
|
+
const rules = { value: { name: '值', type: 'string', min: null, max: null } };
|
|
665
|
+
const result = Validator.validate({ value: 'any length string here' }, rules);
|
|
666
|
+
expect(result.code).toBe(0);
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
test('regexp 为 null 时不检查正则', () => {
|
|
670
|
+
const rules = { value: { name: '值', type: 'string', min: 0, max: 100, regexp: null } };
|
|
671
|
+
const result = Validator.validate({ value: 'anything' }, rules);
|
|
672
|
+
expect(result.code).toBe(0);
|
|
147
673
|
});
|
|
148
674
|
});
|