befly 3.9.38 → 3.9.40
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 +37 -38
- 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} +223 -231
- package/docs/cipher.md +71 -69
- package/docs/database.md +143 -141
- 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} +1 -1
- 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 +8 -8
- package/lib/asyncContext.ts +43 -0
- package/lib/cacheHelper.ts +212 -77
- package/lib/cacheKeys.ts +38 -0
- package/lib/cipher.ts +30 -30
- package/lib/connect.ts +28 -28
- package/lib/dbHelper.ts +183 -102
- 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 +48 -44
- 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 -52
- 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 -65
- package/sync/syncMenu.ts +190 -55
- 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
|
@@ -5,95 +5,95 @@
|
|
|
5
5
|
|
|
6
6
|
// 模拟测试函数(从 dbHelper 复制的逻辑)
|
|
7
7
|
function validateAndClassifyFields(fields?: string[]): {
|
|
8
|
-
type:
|
|
8
|
+
type: "all" | "include" | "exclude";
|
|
9
9
|
fields: string[];
|
|
10
10
|
} {
|
|
11
11
|
// 情况1:空数组或 undefined,表示查询所有
|
|
12
12
|
if (!fields || fields.length === 0) {
|
|
13
|
-
return { type:
|
|
13
|
+
return { type: "all", fields: [] };
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
// 检测是否有星号(禁止)
|
|
17
|
-
if (fields.some((f) => f ===
|
|
18
|
-
throw new Error(
|
|
17
|
+
if (fields.some((f) => f === "*")) {
|
|
18
|
+
throw new Error("fields 不支持 * 星号,请使用空数组 [] 或不传参数表示查询所有字段");
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// 检测是否有空字符串或无效值
|
|
22
|
-
if (fields.some((f) => !f || typeof f !==
|
|
23
|
-
throw new Error(
|
|
22
|
+
if (fields.some((f) => !f || typeof f !== "string" || f.trim() === "")) {
|
|
23
|
+
throw new Error("fields 不能包含空字符串或无效值");
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// 统计包含字段和排除字段
|
|
27
|
-
const includeFields = fields.filter((f) => !f.startsWith(
|
|
28
|
-
const excludeFields = fields.filter((f) => f.startsWith(
|
|
27
|
+
const includeFields = fields.filter((f) => !f.startsWith("!"));
|
|
28
|
+
const excludeFields = fields.filter((f) => f.startsWith("!"));
|
|
29
29
|
|
|
30
30
|
// 情况2:全部是包含字段
|
|
31
31
|
if (includeFields.length > 0 && excludeFields.length === 0) {
|
|
32
|
-
return { type:
|
|
32
|
+
return { type: "include", fields: includeFields };
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// 情况3:全部是排除字段
|
|
36
36
|
if (excludeFields.length > 0 && includeFields.length === 0) {
|
|
37
37
|
// 去掉感叹号前缀
|
|
38
38
|
const cleanExcludeFields = excludeFields.map((f) => f.substring(1));
|
|
39
|
-
return { type:
|
|
39
|
+
return { type: "exclude", fields: cleanExcludeFields };
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// 混用情况:报错
|
|
43
43
|
throw new Error('fields 不能同时包含普通字段和排除字段(! 开头)。只能使用以下3种方式之一:\n1. 空数组 [] 或不传(查询所有)\n2. 全部指定字段 ["id", "name"]\n3. 全部排除字段 ["!password", "!token"]');
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
console.log(
|
|
46
|
+
console.log("\n========== 测试 validateAndClassifyFields ==========\n");
|
|
47
47
|
|
|
48
48
|
// 测试1:空数组
|
|
49
|
-
console.log(
|
|
49
|
+
console.log("【测试1】空数组");
|
|
50
50
|
const test1 = validateAndClassifyFields([]);
|
|
51
|
-
console.log(
|
|
52
|
-
console.log(
|
|
51
|
+
console.log("结果:", test1);
|
|
52
|
+
console.log("✅ 通过\n");
|
|
53
53
|
|
|
54
54
|
// 测试2:undefined
|
|
55
|
-
console.log(
|
|
55
|
+
console.log("【测试2】undefined");
|
|
56
56
|
const test2 = validateAndClassifyFields(undefined);
|
|
57
|
-
console.log(
|
|
58
|
-
console.log(
|
|
57
|
+
console.log("结果:", test2);
|
|
58
|
+
console.log("✅ 通过\n");
|
|
59
59
|
|
|
60
60
|
// 测试3:包含字段
|
|
61
|
-
console.log(
|
|
62
|
-
const test3 = validateAndClassifyFields([
|
|
63
|
-
console.log(
|
|
64
|
-
console.log(
|
|
61
|
+
console.log("【测试3】包含字段");
|
|
62
|
+
const test3 = validateAndClassifyFields(["id", "name", "email"]);
|
|
63
|
+
console.log("结果:", test3);
|
|
64
|
+
console.log("✅ 通过\n");
|
|
65
65
|
|
|
66
66
|
// 测试4:排除字段
|
|
67
|
-
console.log(
|
|
68
|
-
const test4 = validateAndClassifyFields([
|
|
69
|
-
console.log(
|
|
70
|
-
console.log(
|
|
67
|
+
console.log("【测试4】排除字段");
|
|
68
|
+
const test4 = validateAndClassifyFields(["!password", "!salt"]);
|
|
69
|
+
console.log("结果:", test4);
|
|
70
|
+
console.log("✅ 通过\n");
|
|
71
71
|
|
|
72
72
|
// 测试5:混用(应该报错)
|
|
73
|
-
console.log(
|
|
73
|
+
console.log("【测试5】混用(应该报错)");
|
|
74
74
|
try {
|
|
75
|
-
validateAndClassifyFields([
|
|
76
|
-
console.log(
|
|
75
|
+
validateAndClassifyFields(["id", "!password"]);
|
|
76
|
+
console.log("❌ 没有抛出错误\n");
|
|
77
77
|
} catch (error: any) {
|
|
78
|
-
console.log(
|
|
78
|
+
console.log("✅ 成功捕获错误:", error.message, "\n");
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
// 测试6:星号(应该报错)
|
|
82
|
-
console.log(
|
|
82
|
+
console.log("【测试6】星号(应该报错)");
|
|
83
83
|
try {
|
|
84
|
-
validateAndClassifyFields([
|
|
85
|
-
console.log(
|
|
84
|
+
validateAndClassifyFields(["*"]);
|
|
85
|
+
console.log("❌ 没有抛出错误\n");
|
|
86
86
|
} catch (error: any) {
|
|
87
|
-
console.log(
|
|
87
|
+
console.log("✅ 成功捕获错误:", error.message, "\n");
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
// 测试7:空字符串(应该报错)
|
|
91
|
-
console.log(
|
|
91
|
+
console.log("【测试7】空字符串(应该报错)");
|
|
92
92
|
try {
|
|
93
|
-
validateAndClassifyFields([
|
|
94
|
-
console.log(
|
|
93
|
+
validateAndClassifyFields(["id", "", "name"]);
|
|
94
|
+
console.log("❌ 没有抛出错误\n");
|
|
95
95
|
} catch (error: any) {
|
|
96
|
-
console.log(
|
|
96
|
+
console.log("✅ 成功捕获错误:", error.message, "\n");
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
console.log(
|
|
99
|
+
console.log("========== 所有测试完成 ==========\n");
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { getClientIp } from "../utils/getClientIp";
|
|
4
|
+
|
|
5
|
+
describe("utils - getClientIp", () => {
|
|
6
|
+
test("优先使用 x-forwarded-for 的第一个值", () => {
|
|
7
|
+
const req = new Request("http://localhost/api/test", {
|
|
8
|
+
method: "GET",
|
|
9
|
+
headers: {
|
|
10
|
+
"x-forwarded-for": " 8.8.8.8, 1.1.1.1 "
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const ip = getClientIp(req);
|
|
15
|
+
expect(ip).toBe("8.8.8.8");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("x-forwarded-for 为空时回退到 x-real-ip", () => {
|
|
19
|
+
const req = new Request("http://localhost/api/test", {
|
|
20
|
+
method: "GET",
|
|
21
|
+
headers: {
|
|
22
|
+
"x-forwarded-for": " ",
|
|
23
|
+
"x-real-ip": "9.9.9.9"
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const ip = getClientIp(req);
|
|
28
|
+
expect(ip).toBe("9.9.9.9");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("无代理头时使用 server.requestIP(req) 兜底", () => {
|
|
32
|
+
const req = new Request("http://localhost/api/test", {
|
|
33
|
+
method: "GET"
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const server = {
|
|
37
|
+
requestIP(_req: Request) {
|
|
38
|
+
return { address: "7.7.7.7" };
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const ip = getClientIp(req, server);
|
|
43
|
+
expect(ip).toBe("7.7.7.7");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("全都没有时返回 unknown", () => {
|
|
47
|
+
const req = new Request("http://localhost/api/test", {
|
|
48
|
+
method: "GET"
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const ip = getClientIp(req);
|
|
52
|
+
expect(ip).toBe("unknown");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { XMLParser } from "fast-xml-parser";
|
|
4
|
+
|
|
5
|
+
import { Cipher } from "../lib/cipher";
|
|
6
|
+
import { Jwt } from "../lib/jwt";
|
|
7
|
+
import { SqlBuilder } from "../lib/sqlBuilder";
|
|
8
|
+
import { Validator } from "../lib/validator";
|
|
9
|
+
import { keysToCamel } from "../utils/keysToCamel";
|
|
10
|
+
import { keysToSnake } from "../utils/keysToSnake";
|
|
11
|
+
|
|
12
|
+
describe("Integration - 密码验证流程", () => {
|
|
13
|
+
test("用户注册:密码加密 + 验证", async () => {
|
|
14
|
+
const password = "MySecurePass123";
|
|
13
15
|
|
|
14
16
|
// 1. 密码加密
|
|
15
17
|
const hashedPassword = await Cipher.hashPassword(password);
|
|
@@ -21,123 +23,123 @@ describe('Integration - 密码验证流程', () => {
|
|
|
21
23
|
expect(isValid).toBe(true);
|
|
22
24
|
|
|
23
25
|
// 3. 错误密码验证
|
|
24
|
-
const isInvalid = await Cipher.verifyPassword(
|
|
26
|
+
const isInvalid = await Cipher.verifyPassword("WrongPassword", hashedPassword);
|
|
25
27
|
expect(isInvalid).toBe(false);
|
|
26
28
|
});
|
|
27
29
|
});
|
|
28
30
|
|
|
29
|
-
describe(
|
|
31
|
+
describe("Integration - JWT + 权限验证", () => {
|
|
30
32
|
const jwt = new Jwt({
|
|
31
|
-
secret:
|
|
32
|
-
algorithm:
|
|
33
|
-
expiresIn:
|
|
33
|
+
secret: "test-integration-secret",
|
|
34
|
+
algorithm: "HS256",
|
|
35
|
+
expiresIn: "1h"
|
|
34
36
|
});
|
|
35
37
|
|
|
36
|
-
test(
|
|
38
|
+
test("用户登录:JWT 签名 + 验证", () => {
|
|
37
39
|
// 1. 用户登录生成 token
|
|
38
40
|
const payload = {
|
|
39
41
|
userId: 123,
|
|
40
|
-
username:
|
|
41
|
-
roles: [
|
|
42
|
-
permissions: [
|
|
42
|
+
username: "john",
|
|
43
|
+
roles: ["admin", "user"],
|
|
44
|
+
permissions: ["read", "write", "delete"]
|
|
43
45
|
};
|
|
44
46
|
|
|
45
47
|
const token = jwt.sign(payload);
|
|
46
48
|
expect(token).toBeDefined();
|
|
47
|
-
expect(typeof token).toBe(
|
|
49
|
+
expect(typeof token).toBe("string");
|
|
48
50
|
|
|
49
51
|
// 2. 验证 token
|
|
50
52
|
const verified = jwt.verify(token);
|
|
51
53
|
expect(verified.userId).toBe(123);
|
|
52
|
-
expect(verified.username).toBe(
|
|
53
|
-
expect(verified.roles).toContain(
|
|
54
|
-
expect(verified.permissions).toContain(
|
|
54
|
+
expect(verified.username).toBe("john");
|
|
55
|
+
expect(verified.roles).toContain("admin");
|
|
56
|
+
expect(verified.permissions).toContain("write");
|
|
55
57
|
});
|
|
56
58
|
});
|
|
57
59
|
|
|
58
|
-
describe(
|
|
59
|
-
test(
|
|
60
|
+
describe("Integration - 数据验证 + SQL 构建", () => {
|
|
61
|
+
test("API 请求:验证数据 + 构建查询", () => {
|
|
60
62
|
// 1. 验证用户输入
|
|
61
63
|
const userData = {
|
|
62
|
-
email:
|
|
64
|
+
email: "test@example.com",
|
|
63
65
|
age: 25,
|
|
64
|
-
username:
|
|
66
|
+
username: "john"
|
|
65
67
|
};
|
|
66
68
|
|
|
67
69
|
const rules = {
|
|
68
|
-
email: { name:
|
|
69
|
-
age: { name:
|
|
70
|
-
username: { name:
|
|
70
|
+
email: { name: "邮箱", type: "string", regexp: "@email" },
|
|
71
|
+
age: { name: "年龄", type: "number", min: 0, max: 150 },
|
|
72
|
+
username: { name: "用户名", type: "string", min: 2, max: 20 }
|
|
71
73
|
};
|
|
72
74
|
|
|
73
|
-
const validationResult = Validator.validate(userData, rules, [
|
|
75
|
+
const validationResult = Validator.validate(userData, rules, ["email", "username"]);
|
|
74
76
|
expect(validationResult.code).toBe(0);
|
|
75
77
|
|
|
76
78
|
// 2. 验证通过后构建 SQL 查询
|
|
77
79
|
const builder = new SqlBuilder();
|
|
78
|
-
const sqlResult = builder.select([
|
|
80
|
+
const sqlResult = builder.select(["id", "username", "email"]).from("users").where({ email: userData.email }).toSelectSql();
|
|
79
81
|
|
|
80
|
-
expect(sqlResult.sql).toContain(
|
|
81
|
-
expect(sqlResult.sql).toContain(
|
|
82
|
-
expect(sqlResult.sql).toContain(
|
|
83
|
-
expect(sqlResult.params).toContain(
|
|
82
|
+
expect(sqlResult.sql).toContain("SELECT");
|
|
83
|
+
expect(sqlResult.sql).toContain("FROM `users`");
|
|
84
|
+
expect(sqlResult.sql).toContain("WHERE `email` = ?");
|
|
85
|
+
expect(sqlResult.params).toContain("test@example.com");
|
|
84
86
|
});
|
|
85
87
|
|
|
86
|
-
test(
|
|
88
|
+
test("数据插入:验证 + 字段转换 + SQL 构建", () => {
|
|
87
89
|
// 1. 验证数据
|
|
88
90
|
const newUser = {
|
|
89
|
-
userName:
|
|
90
|
-
userEmail:
|
|
91
|
+
userName: "jane",
|
|
92
|
+
userEmail: "jane@example.com",
|
|
91
93
|
userAge: 30
|
|
92
94
|
};
|
|
93
95
|
|
|
94
96
|
const rules = {
|
|
95
|
-
userName: { name:
|
|
96
|
-
userEmail: { name:
|
|
97
|
-
userAge: { name:
|
|
97
|
+
userName: { name: "用户名", type: "string", min: 2, max: 20 },
|
|
98
|
+
userEmail: { name: "邮箱", type: "string", regexp: "@email" },
|
|
99
|
+
userAge: { name: "年龄", type: "number", min: 0, max: 150 }
|
|
98
100
|
};
|
|
99
101
|
|
|
100
|
-
const validationResult = Validator.validate(newUser, rules, [
|
|
102
|
+
const validationResult = Validator.validate(newUser, rules, ["userName", "userEmail"]);
|
|
101
103
|
expect(validationResult.code).toBe(0);
|
|
102
104
|
|
|
103
105
|
// 2. 字段转换(驼峰转下划线)
|
|
104
106
|
const dbData = keysToSnake(newUser);
|
|
105
|
-
expect(dbData.user_name).toBe(
|
|
106
|
-
expect(dbData.user_email).toBe(
|
|
107
|
+
expect(dbData.user_name).toBe("jane");
|
|
108
|
+
expect(dbData.user_email).toBe("jane@example.com");
|
|
107
109
|
expect(dbData.user_age).toBe(30);
|
|
108
110
|
|
|
109
111
|
// 3. 构建插入 SQL
|
|
110
112
|
const builder = new SqlBuilder();
|
|
111
|
-
const sqlResult = builder.toInsertSql(
|
|
113
|
+
const sqlResult = builder.toInsertSql("users", dbData);
|
|
112
114
|
|
|
113
|
-
expect(sqlResult.sql).toContain(
|
|
114
|
-
expect(sqlResult.params).toContain(
|
|
115
|
-
expect(sqlResult.params).toContain(
|
|
115
|
+
expect(sqlResult.sql).toContain("INSERT INTO `users`");
|
|
116
|
+
expect(sqlResult.params).toContain("jane");
|
|
117
|
+
expect(sqlResult.params).toContain("jane@example.com");
|
|
116
118
|
});
|
|
117
119
|
});
|
|
118
120
|
|
|
119
|
-
describe(
|
|
120
|
-
test(
|
|
121
|
+
describe("Integration - XML 解析 + 数据转换", () => {
|
|
122
|
+
test("XML API 响应:解析 + 字段转换", () => {
|
|
121
123
|
const xmlParser = new XMLParser();
|
|
122
124
|
|
|
123
125
|
// 1. 解析 XML 响应
|
|
124
|
-
const xmlData =
|
|
126
|
+
const xmlData = "<response><user_id>123</user_id><user_name>John</user_name><is_active>true</is_active></response>";
|
|
125
127
|
const parsed = xmlParser.parse(xmlData).response as any;
|
|
126
128
|
|
|
127
129
|
expect(parsed.user_id).toBe(123);
|
|
128
|
-
expect(parsed.user_name).toBe(
|
|
130
|
+
expect(parsed.user_name).toBe("John");
|
|
129
131
|
expect(parsed.is_active).toBe(true);
|
|
130
132
|
|
|
131
133
|
// 2. 字段转换(下划线转驼峰)
|
|
132
134
|
const camelData = keysToCamel(parsed);
|
|
133
135
|
expect(camelData.userId).toBe(123);
|
|
134
|
-
expect(camelData.userName).toBe(
|
|
136
|
+
expect(camelData.userName).toBe("John");
|
|
135
137
|
expect(camelData.isActive).toBe(true);
|
|
136
138
|
});
|
|
137
139
|
});
|
|
138
140
|
|
|
139
|
-
describe(
|
|
140
|
-
test(
|
|
141
|
+
describe("Integration - 加密 + JWT + 哈希", () => {
|
|
142
|
+
test("安全令牌生成:多层加密", () => {
|
|
141
143
|
// 1. 生成随机字符串作为 secret
|
|
142
144
|
const secret = Cipher.randomString(32);
|
|
143
145
|
expect(secret.length).toBe(32);
|
|
@@ -147,7 +149,7 @@ describe('Integration - 加密 + JWT + 哈希', () => {
|
|
|
147
149
|
expect(hashedSecret.length).toBe(64);
|
|
148
150
|
|
|
149
151
|
// 3. 使用 HMAC 签名
|
|
150
|
-
const data =
|
|
152
|
+
const data = "user123:session456";
|
|
151
153
|
const signature = Cipher.hmacSha256(data, hashedSecret);
|
|
152
154
|
expect(signature).toBeDefined();
|
|
153
155
|
|
|
@@ -161,14 +163,14 @@ describe('Integration - 加密 + JWT + 哈希', () => {
|
|
|
161
163
|
});
|
|
162
164
|
});
|
|
163
165
|
|
|
164
|
-
describe(
|
|
165
|
-
test(
|
|
166
|
+
describe("Integration - SQL 构建 + 字段转换", () => {
|
|
167
|
+
test("复杂查询:驼峰转下划线 + WHERE 条件 + 排序分页", () => {
|
|
166
168
|
const builder = new SqlBuilder();
|
|
167
169
|
|
|
168
170
|
// 1. 原始查询条件(驼峰格式)
|
|
169
171
|
const queryConditions = {
|
|
170
172
|
userId: 123,
|
|
171
|
-
userStatus:
|
|
173
|
+
userStatus: "active",
|
|
172
174
|
createdAt: { $gte: 1609459200000 }
|
|
173
175
|
};
|
|
174
176
|
|
|
@@ -176,12 +178,12 @@ describe('Integration - SQL 构建 + 字段转换', () => {
|
|
|
176
178
|
const dbConditions = keysToSnake(queryConditions);
|
|
177
179
|
|
|
178
180
|
// 3. 构建复杂查询
|
|
179
|
-
const sqlResult = builder.select([
|
|
181
|
+
const sqlResult = builder.select(["id", "user_name", "user_email", "created_at"]).from("users").where(dbConditions).orderBy(["created_at#DESC"]).limit(20).offset(0).toSelectSql();
|
|
180
182
|
|
|
181
|
-
expect(sqlResult.sql).toContain(
|
|
182
|
-
expect(sqlResult.sql).toContain(
|
|
183
|
-
expect(sqlResult.sql).toContain(
|
|
184
|
-
expect(sqlResult.sql).toContain(
|
|
183
|
+
expect(sqlResult.sql).toContain("SELECT");
|
|
184
|
+
expect(sqlResult.sql).toContain("WHERE");
|
|
185
|
+
expect(sqlResult.sql).toContain("ORDER BY");
|
|
186
|
+
expect(sqlResult.sql).toContain("LIMIT 20");
|
|
185
187
|
expect(sqlResult.params.length).toBeGreaterThan(0);
|
|
186
188
|
});
|
|
187
189
|
});
|
package/tests/jwt.test.ts
CHANGED
|
@@ -1,60 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { Jwt } from "../lib/jwt";
|
|
3
4
|
|
|
4
5
|
const jwt = new Jwt({
|
|
5
|
-
secret:
|
|
6
|
-
algorithm:
|
|
7
|
-
expiresIn:
|
|
6
|
+
secret: "test-secret",
|
|
7
|
+
algorithm: "HS256",
|
|
8
|
+
expiresIn: "7d"
|
|
8
9
|
});
|
|
9
10
|
|
|
10
|
-
describe(
|
|
11
|
-
test(
|
|
12
|
-
const token = jwt.sign({ userId: 1, name:
|
|
13
|
-
expect(typeof token).toBe(
|
|
14
|
-
expect(token.split(
|
|
11
|
+
describe("JWT - sign", () => {
|
|
12
|
+
test("签名创建 token", () => {
|
|
13
|
+
const token = jwt.sign({ userId: 1, name: "test" });
|
|
14
|
+
expect(typeof token).toBe("string");
|
|
15
|
+
expect(token.split(".").length).toBe(3);
|
|
15
16
|
});
|
|
16
17
|
|
|
17
|
-
test(
|
|
18
|
-
const secret =
|
|
18
|
+
test("自定义密钥", () => {
|
|
19
|
+
const secret = "custom-secret";
|
|
19
20
|
const token = jwt.sign({ userId: 1 }, { secret: secret });
|
|
20
21
|
const decoded = jwt.verify(token, { secret: secret });
|
|
21
22
|
expect(decoded.userId).toBe(1);
|
|
22
23
|
});
|
|
23
24
|
});
|
|
24
25
|
|
|
25
|
-
describe(
|
|
26
|
-
test(
|
|
27
|
-
const token = jwt.sign({ userId: 123, email:
|
|
26
|
+
describe("JWT - verify", () => {
|
|
27
|
+
test("验证有效 token", () => {
|
|
28
|
+
const token = jwt.sign({ userId: 123, email: "test@test.com" });
|
|
28
29
|
const decoded = jwt.verify(token);
|
|
29
30
|
expect(decoded.userId).toBe(123);
|
|
30
|
-
expect(decoded.email).toBe(
|
|
31
|
+
expect(decoded.email).toBe("test@test.com");
|
|
31
32
|
});
|
|
32
33
|
|
|
33
|
-
test(
|
|
34
|
-
const token = jwt.sign({ userId: 1 }, { expiresIn:
|
|
34
|
+
test("验证过期 token 抛出错误", () => {
|
|
35
|
+
const token = jwt.sign({ userId: 1 }, { expiresIn: "-1s" });
|
|
35
36
|
expect(() => jwt.verify(token)).toThrow();
|
|
36
37
|
});
|
|
37
38
|
|
|
38
|
-
test(
|
|
39
|
-
const token = jwt.sign({ userId: 1 }, { expiresIn:
|
|
39
|
+
test("忽略过期验证", () => {
|
|
40
|
+
const token = jwt.sign({ userId: 1 }, { expiresIn: "-1s" });
|
|
40
41
|
const decoded = jwt.verify(token, { ignoreExpiration: true });
|
|
41
42
|
expect(decoded.userId).toBe(1);
|
|
42
43
|
});
|
|
43
44
|
|
|
44
|
-
test(
|
|
45
|
-
const token = jwt.sign({ userId: 1 }, { secret:
|
|
46
|
-
expect(() => jwt.verify(token, { secret:
|
|
45
|
+
test("密钥不匹配验证失败", () => {
|
|
46
|
+
const token = jwt.sign({ userId: 1 }, { secret: "secret1" });
|
|
47
|
+
expect(() => jwt.verify(token, { secret: "secret2" })).toThrow();
|
|
47
48
|
});
|
|
48
49
|
});
|
|
49
50
|
|
|
50
|
-
describe(
|
|
51
|
-
test(
|
|
51
|
+
describe("JWT - decode", () => {
|
|
52
|
+
test("解码不验证签名", () => {
|
|
52
53
|
const token = jwt.sign({ userId: 456 });
|
|
53
54
|
const decoded = jwt.decode(token);
|
|
54
55
|
expect(decoded.userId).toBe(456);
|
|
55
56
|
});
|
|
56
57
|
|
|
57
|
-
test(
|
|
58
|
+
test("解码完整信息", () => {
|
|
58
59
|
const token = jwt.sign({ userId: 1 });
|
|
59
60
|
const decoded = jwt.decode(token, true);
|
|
60
61
|
expect(decoded.header).toBeDefined();
|