befly 3.8.20 → 3.8.24
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/checks/checkApi.ts +92 -0
- package/checks/checkApp.ts +31 -0
- package/checks/checkTable.ts +247 -0
- package/config.ts +71 -0
- package/hooks/auth.ts +30 -0
- package/hooks/cors.ts +48 -0
- package/hooks/errorHandler.ts +23 -0
- package/hooks/parser.ts +67 -0
- package/hooks/permission.ts +54 -0
- package/hooks/rateLimit.ts +70 -0
- package/hooks/requestId.ts +24 -0
- package/hooks/requestLogger.ts +25 -0
- package/hooks/responseFormatter.ts +64 -0
- package/hooks/validator.ts +34 -0
- package/package.json +15 -14
- package/tests/cipher.test.ts +248 -0
- package/tests/dbHelper-advanced.test.ts +717 -0
- package/tests/dbHelper-columns.test.ts +266 -0
- package/tests/dbHelper-execute.test.ts +240 -0
- package/tests/fields-redis-cache.test.ts +123 -0
- package/tests/fields-validate.test.ts +99 -0
- package/tests/integration.test.ts +202 -0
- package/tests/jwt.test.ts +122 -0
- package/tests/logger.test.ts +94 -0
- package/tests/redisHelper.test.ts +231 -0
- package/tests/sqlBuilder-advanced.test.ts +593 -0
- package/tests/sqlBuilder.test.ts +184 -0
- package/tests/util.test.ts +95 -0
- package/tests/validator-advanced.test.ts +653 -0
- package/tests/validator.test.ts +148 -0
- package/tests/xml.test.ts +101 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// 相对导入
|
|
2
|
+
import { Logger } from '../lib/logger.js';
|
|
3
|
+
import { JsonResponse } from '../util.js';
|
|
4
|
+
|
|
5
|
+
// 类型导入
|
|
6
|
+
import type { Hook } from '../types/hook.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 接口限流插件
|
|
10
|
+
*
|
|
11
|
+
* 功能:
|
|
12
|
+
* 1. 基于 Redis 实现滑动窗口或固定窗口限流
|
|
13
|
+
* 2. 支持配置格式 "count/seconds" (e.g. "10/60")
|
|
14
|
+
* 3. 针对每个用户(userId)或IP进行限制
|
|
15
|
+
*/
|
|
16
|
+
const hook: Hook = {
|
|
17
|
+
// 必须在 auth 之后(获取 userId),但在业务逻辑之前
|
|
18
|
+
after: ['parser'],
|
|
19
|
+
order: 25,
|
|
20
|
+
|
|
21
|
+
handler: async (befly, ctx, next) => {
|
|
22
|
+
const { api } = ctx;
|
|
23
|
+
|
|
24
|
+
// 1. 检查配置
|
|
25
|
+
if (!api || !api.rateLimit || !befly.redis) {
|
|
26
|
+
return next();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 2. 解析配置 "10/60" -> count=10, seconds=60
|
|
30
|
+
const [countStr, secondsStr] = api.rateLimit.split('/');
|
|
31
|
+
const limitCount = parseInt(countStr, 10);
|
|
32
|
+
const limitSeconds = parseInt(secondsStr, 10);
|
|
33
|
+
|
|
34
|
+
if (isNaN(limitCount) || isNaN(limitSeconds)) {
|
|
35
|
+
Logger.warn(`[RateLimit] Invalid config: ${api.rateLimit}`);
|
|
36
|
+
return next();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 3. 生成 Key
|
|
40
|
+
// 优先使用 userId,否则使用 IP
|
|
41
|
+
const identifier = ctx.user?.userId || ctx.ip || 'unknown';
|
|
42
|
+
const apiPath = ctx.route || `${ctx.req.method}${new URL(ctx.req.url).pathname}`;
|
|
43
|
+
const key = `rate_limit:${apiPath}:${identifier}`;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
// 4. 执行限流逻辑 (使用 Redis INCR)
|
|
47
|
+
// 这是一个简单的固定窗口算法,对于严格场景可能需要 Lua 脚本实现滑动窗口
|
|
48
|
+
const current = await befly.redis.incr(key);
|
|
49
|
+
|
|
50
|
+
// 5. 设置过期时间 (如果是新 Key)
|
|
51
|
+
if (current === 1) {
|
|
52
|
+
await befly.redis.expire(key, limitSeconds);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 6. 判断是否超限
|
|
56
|
+
if (current > limitCount) {
|
|
57
|
+
ctx.response = JsonResponse(ctx, '请求过于频繁,请稍后再试', 429);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return next();
|
|
62
|
+
} catch (err) {
|
|
63
|
+
Logger.error('[RateLimit] Redis error:', err);
|
|
64
|
+
// Redis 故障时,默认放行,避免阻塞业务
|
|
65
|
+
return next();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export default hook;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Hook } from '../types/hook.js';
|
|
2
|
+
import { logContextStorage } from '../lib/logger.js';
|
|
3
|
+
|
|
4
|
+
const hook: Hook = {
|
|
5
|
+
after: ['errorHandler'],
|
|
6
|
+
order: 3,
|
|
7
|
+
handler: async (befly, ctx, next) => {
|
|
8
|
+
// 生成唯一请求 ID
|
|
9
|
+
const requestId = crypto.randomUUID();
|
|
10
|
+
ctx.requestId = requestId;
|
|
11
|
+
|
|
12
|
+
// 添加到 CORS 响应头
|
|
13
|
+
if (!ctx.corsHeaders) {
|
|
14
|
+
ctx.corsHeaders = {};
|
|
15
|
+
}
|
|
16
|
+
ctx.corsHeaders['X-Request-ID'] = requestId;
|
|
17
|
+
|
|
18
|
+
// 在 AsyncLocalStorage 上下文中执行后续钩子
|
|
19
|
+
await logContextStorage.run({ requestId }, async () => {
|
|
20
|
+
await next();
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
export default hook;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// 相对导入
|
|
2
|
+
import { Logger } from '../lib/logger.js';
|
|
3
|
+
|
|
4
|
+
// 类型导入
|
|
5
|
+
import type { Hook } from '../types/hook.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 请求日志记录钩子
|
|
9
|
+
* 记录请求方法、路径、用户信息和响应时间
|
|
10
|
+
*/
|
|
11
|
+
const hook: Hook = {
|
|
12
|
+
after: ['parser'],
|
|
13
|
+
order: 30,
|
|
14
|
+
handler: async (befly, ctx, next) => {
|
|
15
|
+
await next();
|
|
16
|
+
|
|
17
|
+
if (ctx.api) {
|
|
18
|
+
const apiPath = `${ctx.req.method}${new URL(ctx.req.url).pathname}`;
|
|
19
|
+
const duration = Date.now() - ctx.now;
|
|
20
|
+
const user = ctx.user?.userId ? `[User:${ctx.user.userId}]` : '[Guest]';
|
|
21
|
+
Logger.info(`[${ctx.req.method}] ${apiPath} ${user} ${duration}ms`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
export default hook;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// 相对导入
|
|
2
|
+
import { JsonResponse } from '../util.js';
|
|
3
|
+
|
|
4
|
+
// 类型导入
|
|
5
|
+
import type { Hook } from '../types/hook.js';
|
|
6
|
+
|
|
7
|
+
const hook: Hook = {
|
|
8
|
+
after: ['requestId'],
|
|
9
|
+
order: 100,
|
|
10
|
+
handler: async (befly, ctx, next) => {
|
|
11
|
+
await next();
|
|
12
|
+
|
|
13
|
+
// 如果已经有 response,直接返回
|
|
14
|
+
if (ctx.response) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 如果有 result,格式化为 JSON 响应
|
|
19
|
+
if (ctx.result !== undefined) {
|
|
20
|
+
let result = ctx.result;
|
|
21
|
+
|
|
22
|
+
// 1. 如果是字符串,自动包裹为成功响应
|
|
23
|
+
if (typeof result === 'string') {
|
|
24
|
+
result = {
|
|
25
|
+
code: 0,
|
|
26
|
+
msg: result,
|
|
27
|
+
data: {}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// 2. 如果是对象,自动补充 code: 0
|
|
31
|
+
else if (result && typeof result === 'object') {
|
|
32
|
+
if (!('code' in result)) {
|
|
33
|
+
result = {
|
|
34
|
+
code: 0,
|
|
35
|
+
...result
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 处理 BigInt 序列化问题
|
|
41
|
+
if (result && typeof result === 'object') {
|
|
42
|
+
const jsonString = JSON.stringify(result, (key, value) => (typeof value === 'bigint' ? value.toString() : value));
|
|
43
|
+
ctx.response = new Response(jsonString, {
|
|
44
|
+
headers: {
|
|
45
|
+
...ctx.corsHeaders,
|
|
46
|
+
'Content-Type': 'application/json'
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
// 简单类型直接返回
|
|
51
|
+
ctx.response = Response.json(result, {
|
|
52
|
+
headers: ctx.corsHeaders
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 如果还没有响应,且不是 OPTIONS 请求,则设置默认 JSON 响应
|
|
59
|
+
if (ctx.req.method !== 'OPTIONS' && !ctx.response) {
|
|
60
|
+
ctx.response = JsonResponse(ctx, 'No response generated');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
export default hook;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// 相对导入
|
|
2
|
+
import { Validator } from '../lib/validator.js';
|
|
3
|
+
import { JsonResponse } from '../util.js';
|
|
4
|
+
|
|
5
|
+
// 类型导入
|
|
6
|
+
import type { Hook } from '../types/hook.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 参数验证钩子
|
|
10
|
+
* 根据 API 定义的 fields 和 required 验证请求参数
|
|
11
|
+
*/
|
|
12
|
+
const hook: Hook = {
|
|
13
|
+
after: ['parser'],
|
|
14
|
+
order: 15,
|
|
15
|
+
handler: async (befly, ctx, next) => {
|
|
16
|
+
if (!ctx.api) return next();
|
|
17
|
+
|
|
18
|
+
// 无需验证
|
|
19
|
+
if (!ctx.api.fields) {
|
|
20
|
+
return next();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 验证参数
|
|
24
|
+
const result = Validator.validate(ctx.body, ctx.api.fields, ctx.api.required || []);
|
|
25
|
+
|
|
26
|
+
if (result.code !== 0) {
|
|
27
|
+
ctx.response = JsonResponse(ctx, '无效的请求参数格式', 1, result.fields);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await next();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
export default hook;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.24",
|
|
4
4
|
"description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -39,38 +39,39 @@
|
|
|
39
39
|
"homepage": "https://chensuiyi.me",
|
|
40
40
|
"license": "Apache-2.0",
|
|
41
41
|
"files": [
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
42
|
+
"checks",
|
|
43
|
+
"dist",
|
|
44
|
+
"hooks",
|
|
45
|
+
"lib",
|
|
46
|
+
"loader",
|
|
47
|
+
"plugins",
|
|
48
|
+
"router",
|
|
49
|
+
"sync",
|
|
50
|
+
"tests",
|
|
51
|
+
"types",
|
|
49
52
|
".npmrc",
|
|
50
53
|
".prettierignore",
|
|
51
54
|
".prettierrc",
|
|
52
55
|
"bunfig.toml",
|
|
53
|
-
"
|
|
56
|
+
"config.ts",
|
|
54
57
|
"LICENSE",
|
|
55
58
|
"main.ts",
|
|
56
|
-
"check.ts",
|
|
57
59
|
"paths.ts",
|
|
58
|
-
"response.ts",
|
|
59
|
-
"dist/",
|
|
60
60
|
"package.json",
|
|
61
61
|
"README.md",
|
|
62
|
+
"tsconfig.json",
|
|
62
63
|
"LICENSE"
|
|
63
64
|
],
|
|
64
65
|
"engines": {
|
|
65
66
|
"bun": ">=1.3.0"
|
|
66
67
|
},
|
|
67
68
|
"dependencies": {
|
|
68
|
-
"befly-util": "0.
|
|
69
|
+
"befly-util": "^1.0.3",
|
|
69
70
|
"chalk": "^5.6.2",
|
|
70
71
|
"es-toolkit": "^1.41.0",
|
|
71
72
|
"pathe": "^2.0.3"
|
|
72
73
|
},
|
|
73
|
-
"gitHead": "
|
|
74
|
+
"gitHead": "6ef0daf22630fda66543e730e0fe244ac401dcfb",
|
|
74
75
|
"devDependencies": {
|
|
75
76
|
"typescript": "^5.9.3"
|
|
76
77
|
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cipher 加密工具测试
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, test, expect, beforeAll } from 'bun:test';
|
|
6
|
+
import { Cipher } from '../lib/cipher';
|
|
7
|
+
import { writeFileSync, unlinkSync, existsSync, mkdirSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
|
|
10
|
+
describe('Cipher - 哈希功能', () => {
|
|
11
|
+
const testData = 'hello world';
|
|
12
|
+
const testDataBytes = new TextEncoder().encode(testData);
|
|
13
|
+
|
|
14
|
+
test('MD5 哈希 - 字符串输入', () => {
|
|
15
|
+
const result = Cipher.md5(testData);
|
|
16
|
+
expect(result).toBe('5eb63bbbe01eeed093cb22bb8f5acdc3');
|
|
17
|
+
expect(result.length).toBe(32);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('MD5 哈希 - Uint8Array 输入', () => {
|
|
21
|
+
const result = Cipher.md5(testDataBytes);
|
|
22
|
+
expect(result).toBe('5eb63bbbe01eeed093cb22bb8f5acdc3');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('MD5 哈希 - base64 编码', () => {
|
|
26
|
+
const result = Cipher.md5(testData, 'base64');
|
|
27
|
+
expect(typeof result).toBe('string');
|
|
28
|
+
expect(result.length).toBeGreaterThan(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('SHA1 哈希', () => {
|
|
32
|
+
const result = Cipher.sha1(testData);
|
|
33
|
+
expect(result).toBe('2aae6c35c94fcfb415dbe95f408b9ce91ee846ed');
|
|
34
|
+
expect(result.length).toBe(40);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('SHA256 哈希', () => {
|
|
38
|
+
const result = Cipher.sha256(testData);
|
|
39
|
+
expect(result).toBe('b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9');
|
|
40
|
+
expect(result.length).toBe(64);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('SHA512 哈希', () => {
|
|
44
|
+
const result = Cipher.sha512(testData);
|
|
45
|
+
expect(result.length).toBe(128);
|
|
46
|
+
expect(typeof result).toBe('string');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('通用 hash 方法 - SHA256', () => {
|
|
50
|
+
const result = Cipher.hash('sha256', testData);
|
|
51
|
+
expect(result).toBe('b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('通用 hash 方法 - MD5', () => {
|
|
55
|
+
const result = Cipher.hash('md5', testData);
|
|
56
|
+
expect(result).toBe('5eb63bbbe01eeed093cb22bb8f5acdc3');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('Cipher - HMAC 签名', () => {
|
|
61
|
+
const key = 'secret-key';
|
|
62
|
+
const data = 'test data';
|
|
63
|
+
|
|
64
|
+
test('HMAC-MD5', () => {
|
|
65
|
+
const result = Cipher.hmacMd5(key, data);
|
|
66
|
+
expect(result.length).toBe(32);
|
|
67
|
+
expect(typeof result).toBe('string');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('HMAC-SHA1', () => {
|
|
71
|
+
const result = Cipher.hmacSha1(key, data);
|
|
72
|
+
expect(result.length).toBe(40);
|
|
73
|
+
expect(typeof result).toBe('string');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('HMAC-SHA256', () => {
|
|
77
|
+
const result = Cipher.hmacSha256(key, data);
|
|
78
|
+
expect(result.length).toBe(64);
|
|
79
|
+
expect(typeof result).toBe('string');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('HMAC-SHA512', () => {
|
|
83
|
+
const result = Cipher.hmacSha512(key, data);
|
|
84
|
+
expect(result.length).toBe(128);
|
|
85
|
+
expect(typeof result).toBe('string');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('通用 HMAC 方法', () => {
|
|
89
|
+
const result1 = Cipher.hmac('sha256', key, data);
|
|
90
|
+
const result2 = Cipher.hmacSha256(key, data);
|
|
91
|
+
expect(result1).toBe(result2);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('HMAC - Uint8Array 输入', () => {
|
|
95
|
+
const keyBytes = new TextEncoder().encode(key);
|
|
96
|
+
const dataBytes = new TextEncoder().encode(data);
|
|
97
|
+
const result = Cipher.hmacSha256(keyBytes, dataBytes);
|
|
98
|
+
expect(result.length).toBe(64);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('Cipher - 密码加密', () => {
|
|
103
|
+
const password = 'MySecurePassword123!';
|
|
104
|
+
|
|
105
|
+
test('密码哈希', async () => {
|
|
106
|
+
const hash = await Cipher.hashPassword(password);
|
|
107
|
+
expect(hash).toBeDefined();
|
|
108
|
+
expect(hash.length).toBeGreaterThan(0);
|
|
109
|
+
expect(hash).not.toBe(password);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('密码验证 - 正确密码', async () => {
|
|
113
|
+
const hash = await Cipher.hashPassword(password);
|
|
114
|
+
const isValid = await Cipher.verifyPassword(password, hash);
|
|
115
|
+
expect(isValid).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('密码验证 - 错误密码', async () => {
|
|
119
|
+
const hash = await Cipher.hashPassword(password);
|
|
120
|
+
const isValid = await Cipher.verifyPassword('wrong-password', hash);
|
|
121
|
+
expect(isValid).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('相同密码生成不同哈希', async () => {
|
|
125
|
+
const hash1 = await Cipher.hashPassword(password);
|
|
126
|
+
const hash2 = await Cipher.hashPassword(password);
|
|
127
|
+
expect(hash1).not.toBe(hash2);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('Cipher - Base64 编码', () => {
|
|
132
|
+
const original = 'Hello, 世界!';
|
|
133
|
+
|
|
134
|
+
test('Base64 编码', () => {
|
|
135
|
+
const encoded = Cipher.base64Encode(original);
|
|
136
|
+
expect(encoded).toBe('SGVsbG8sIOS4lueVjCE=');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('Base64 解码', () => {
|
|
140
|
+
const encoded = 'SGVsbG8sIOS4lueVjCE=';
|
|
141
|
+
const decoded = Cipher.base64Decode(encoded);
|
|
142
|
+
expect(decoded).toBe(original);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('Base64 编码解码循环', () => {
|
|
146
|
+
const encoded = Cipher.base64Encode(original);
|
|
147
|
+
const decoded = Cipher.base64Decode(encoded);
|
|
148
|
+
expect(decoded).toBe(original);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('Base64 编码空字符串', () => {
|
|
152
|
+
const encoded = Cipher.base64Encode('');
|
|
153
|
+
expect(encoded).toBe('');
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('Cipher - 随机字符串', () => {
|
|
158
|
+
test('生成指定长度的随机字符串', () => {
|
|
159
|
+
const result = Cipher.randomString(16);
|
|
160
|
+
expect(result.length).toBe(16);
|
|
161
|
+
expect(/^[0-9a-f]+$/.test(result)).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('生成不同的随机字符串', () => {
|
|
165
|
+
const result1 = Cipher.randomString(32);
|
|
166
|
+
const result2 = Cipher.randomString(32);
|
|
167
|
+
expect(result1).not.toBe(result2);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('生成奇数长度的随机字符串', () => {
|
|
171
|
+
const result = Cipher.randomString(15);
|
|
172
|
+
expect(result.length).toBe(15);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('生成 0 长度字符串', () => {
|
|
176
|
+
const result = Cipher.randomString(0);
|
|
177
|
+
expect(result).toBe('');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('Cipher - 文件哈希', () => {
|
|
182
|
+
const testFilePath = join(process.cwd(), 'temp', 'cipher-test-file.txt');
|
|
183
|
+
const testContent = 'Test file content for hashing';
|
|
184
|
+
|
|
185
|
+
beforeAll(() => {
|
|
186
|
+
const tempDir = join(process.cwd(), 'temp');
|
|
187
|
+
if (!existsSync(tempDir)) {
|
|
188
|
+
mkdirSync(tempDir, { recursive: true });
|
|
189
|
+
}
|
|
190
|
+
writeFileSync(testFilePath, testContent, 'utf8');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('SHA256 文件哈希', async () => {
|
|
194
|
+
const result = await Cipher.hashFile(testFilePath, 'sha256');
|
|
195
|
+
expect(result.length).toBe(64);
|
|
196
|
+
expect(typeof result).toBe('string');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('MD5 文件哈希', async () => {
|
|
200
|
+
const result = await Cipher.hashFile(testFilePath, 'md5');
|
|
201
|
+
expect(result.length).toBe(32);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('文件哈希与内容哈希一致', async () => {
|
|
205
|
+
const fileHash = await Cipher.hashFile(testFilePath, 'sha256');
|
|
206
|
+
const contentHash = Cipher.sha256(testContent);
|
|
207
|
+
expect(fileHash).toBe(contentHash);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('文件哈希 - base64 编码', async () => {
|
|
211
|
+
const result = await Cipher.hashFile(testFilePath, 'sha256', 'base64');
|
|
212
|
+
expect(typeof result).toBe('string');
|
|
213
|
+
expect(result.length).toBeGreaterThan(0);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('Cipher - 快速哈希', () => {
|
|
218
|
+
const testData = 'quick hash test';
|
|
219
|
+
|
|
220
|
+
test('快速哈希 - 字符串输入', () => {
|
|
221
|
+
const result = Cipher.fastHash(testData);
|
|
222
|
+
expect(typeof result).toBe('bigint');
|
|
223
|
+
expect(result).toBeGreaterThan(0n);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('快速哈希 - Uint8Array 输入', () => {
|
|
227
|
+
const bytes = new TextEncoder().encode(testData);
|
|
228
|
+
const result = Cipher.fastHash(bytes);
|
|
229
|
+
expect(typeof result).toBe('bigint');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('快速哈希 - 相同输入产生相同哈希', () => {
|
|
233
|
+
const result1 = Cipher.fastHash(testData);
|
|
234
|
+
const result2 = Cipher.fastHash(testData);
|
|
235
|
+
expect(result1).toBe(result2);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('快速哈希 - 不同 seed 产生不同结果', () => {
|
|
239
|
+
const result1 = Cipher.fastHash(testData, 0);
|
|
240
|
+
const result2 = Cipher.fastHash(testData, 1);
|
|
241
|
+
expect(result1).not.toBe(result2);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('快速哈希 - 空字符串', () => {
|
|
245
|
+
const result = Cipher.fastHash('');
|
|
246
|
+
expect(typeof result).toBe('bigint');
|
|
247
|
+
});
|
|
248
|
+
});
|