befly 3.8.20 → 3.8.21
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,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 简单测试字段排除功能
|
|
3
|
+
* 直接测试 validateAndClassifyFields 方法
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// 模拟测试函数(从 dbHelper 复制的逻辑)
|
|
7
|
+
function validateAndClassifyFields(fields?: string[]): {
|
|
8
|
+
type: 'all' | 'include' | 'exclude';
|
|
9
|
+
fields: string[];
|
|
10
|
+
} {
|
|
11
|
+
// 情况1:空数组或 undefined,表示查询所有
|
|
12
|
+
if (!fields || fields.length === 0) {
|
|
13
|
+
return { type: 'all', fields: [] };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// 检测是否有星号(禁止)
|
|
17
|
+
if (fields.some((f) => f === '*')) {
|
|
18
|
+
throw new Error('fields 不支持 * 星号,请使用空数组 [] 或不传参数表示查询所有字段');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 检测是否有空字符串或无效值
|
|
22
|
+
if (fields.some((f) => !f || typeof f !== 'string' || f.trim() === '')) {
|
|
23
|
+
throw new Error('fields 不能包含空字符串或无效值');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 统计包含字段和排除字段
|
|
27
|
+
const includeFields = fields.filter((f) => !f.startsWith('!'));
|
|
28
|
+
const excludeFields = fields.filter((f) => f.startsWith('!'));
|
|
29
|
+
|
|
30
|
+
// 情况2:全部是包含字段
|
|
31
|
+
if (includeFields.length > 0 && excludeFields.length === 0) {
|
|
32
|
+
return { type: 'include', fields: includeFields };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 情况3:全部是排除字段
|
|
36
|
+
if (excludeFields.length > 0 && includeFields.length === 0) {
|
|
37
|
+
// 去掉感叹号前缀
|
|
38
|
+
const cleanExcludeFields = excludeFields.map((f) => f.substring(1));
|
|
39
|
+
return { type: 'exclude', fields: cleanExcludeFields };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 混用情况:报错
|
|
43
|
+
throw new Error('fields 不能同时包含普通字段和排除字段(! 开头)。只能使用以下3种方式之一:\n1. 空数组 [] 或不传(查询所有)\n2. 全部指定字段 ["id", "name"]\n3. 全部排除字段 ["!password", "!token"]');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log('\n========== 测试 validateAndClassifyFields ==========\n');
|
|
47
|
+
|
|
48
|
+
// 测试1:空数组
|
|
49
|
+
console.log('【测试1】空数组');
|
|
50
|
+
const test1 = validateAndClassifyFields([]);
|
|
51
|
+
console.log('结果:', test1);
|
|
52
|
+
console.log('✅ 通过\n');
|
|
53
|
+
|
|
54
|
+
// 测试2:undefined
|
|
55
|
+
console.log('【测试2】undefined');
|
|
56
|
+
const test2 = validateAndClassifyFields(undefined);
|
|
57
|
+
console.log('结果:', test2);
|
|
58
|
+
console.log('✅ 通过\n');
|
|
59
|
+
|
|
60
|
+
// 测试3:包含字段
|
|
61
|
+
console.log('【测试3】包含字段');
|
|
62
|
+
const test3 = validateAndClassifyFields(['id', 'name', 'email']);
|
|
63
|
+
console.log('结果:', test3);
|
|
64
|
+
console.log('✅ 通过\n');
|
|
65
|
+
|
|
66
|
+
// 测试4:排除字段
|
|
67
|
+
console.log('【测试4】排除字段');
|
|
68
|
+
const test4 = validateAndClassifyFields(['!password', '!salt']);
|
|
69
|
+
console.log('结果:', test4);
|
|
70
|
+
console.log('✅ 通过\n');
|
|
71
|
+
|
|
72
|
+
// 测试5:混用(应该报错)
|
|
73
|
+
console.log('【测试5】混用(应该报错)');
|
|
74
|
+
try {
|
|
75
|
+
validateAndClassifyFields(['id', '!password']);
|
|
76
|
+
console.log('❌ 没有抛出错误\n');
|
|
77
|
+
} catch (error: any) {
|
|
78
|
+
console.log('✅ 成功捕获错误:', error.message, '\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 测试6:星号(应该报错)
|
|
82
|
+
console.log('【测试6】星号(应该报错)');
|
|
83
|
+
try {
|
|
84
|
+
validateAndClassifyFields(['*']);
|
|
85
|
+
console.log('❌ 没有抛出错误\n');
|
|
86
|
+
} catch (error: any) {
|
|
87
|
+
console.log('✅ 成功捕获错误:', error.message, '\n');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 测试7:空字符串(应该报错)
|
|
91
|
+
console.log('【测试7】空字符串(应该报错)');
|
|
92
|
+
try {
|
|
93
|
+
validateAndClassifyFields(['id', '', 'name']);
|
|
94
|
+
console.log('❌ 没有抛出错误\n');
|
|
95
|
+
} catch (error: any) {
|
|
96
|
+
console.log('✅ 成功捕获错误:', error.message, '\n');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log('========== 所有测试完成 ==========\n');
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { describe, test, expect, beforeAll } from 'bun:test';
|
|
2
|
+
import { Cipher } from '../lib/cipher';
|
|
3
|
+
import { Jwt } from '../lib/jwt';
|
|
4
|
+
import { Validator } from '../lib/validator';
|
|
5
|
+
import { SqlBuilder } from '../lib/sqlBuilder';
|
|
6
|
+
import { Xml } from '../lib/xml';
|
|
7
|
+
import { keysToCamel, keysToSnake } from 'befly-util';
|
|
8
|
+
|
|
9
|
+
describe('Integration - 密码验证流程', () => {
|
|
10
|
+
test('用户注册:密码加密 + 验证', async () => {
|
|
11
|
+
const password = 'MySecurePass123';
|
|
12
|
+
|
|
13
|
+
// 1. 密码加密
|
|
14
|
+
const hashedPassword = await Cipher.hashPassword(password);
|
|
15
|
+
expect(hashedPassword).toBeDefined();
|
|
16
|
+
expect(hashedPassword.length).toBeGreaterThan(0);
|
|
17
|
+
|
|
18
|
+
// 2. 密码验证
|
|
19
|
+
const isValid = await Cipher.verifyPassword(password, hashedPassword);
|
|
20
|
+
expect(isValid).toBe(true);
|
|
21
|
+
|
|
22
|
+
// 3. 错误密码验证
|
|
23
|
+
const isInvalid = await Cipher.verifyPassword('WrongPassword', hashedPassword);
|
|
24
|
+
expect(isInvalid).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('Integration - JWT + 权限验证', () => {
|
|
29
|
+
beforeAll(() => {
|
|
30
|
+
Jwt.configure({
|
|
31
|
+
secret: 'test-integration-secret',
|
|
32
|
+
algorithm: 'HS256',
|
|
33
|
+
expiresIn: '1h'
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('用户登录:JWT 签名 + 权限检查', async () => {
|
|
38
|
+
// 1. 用户登录生成 token
|
|
39
|
+
const payload = {
|
|
40
|
+
userId: 123,
|
|
41
|
+
username: 'john',
|
|
42
|
+
roles: ['admin', 'user'],
|
|
43
|
+
permissions: ['read', 'write', 'delete']
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const token = await Jwt.sign(payload);
|
|
47
|
+
expect(token).toBeDefined();
|
|
48
|
+
expect(typeof token).toBe('string');
|
|
49
|
+
|
|
50
|
+
// 2. 验证 token
|
|
51
|
+
const verified = await Jwt.verify(token);
|
|
52
|
+
expect(verified.userId).toBe(123);
|
|
53
|
+
expect(verified.username).toBe('john');
|
|
54
|
+
|
|
55
|
+
// 3. 检查角色
|
|
56
|
+
expect(Jwt.hasRole(verified, 'admin')).toBe(true);
|
|
57
|
+
expect(Jwt.hasRole(verified, 'guest')).toBe(false);
|
|
58
|
+
|
|
59
|
+
// 4. 检查权限
|
|
60
|
+
expect(Jwt.hasPermission(verified, 'write')).toBe(true);
|
|
61
|
+
expect(Jwt.hasPermission(verified, 'admin')).toBe(false);
|
|
62
|
+
|
|
63
|
+
// 5. 检查所有权限
|
|
64
|
+
expect(Jwt.hasAllPermissions(verified, ['read', 'write'])).toBe(true);
|
|
65
|
+
expect(Jwt.hasAllPermissions(verified, ['read', 'admin'])).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('Integration - 数据验证 + SQL 构建', () => {
|
|
70
|
+
test('API 请求:验证数据 + 构建查询', () => {
|
|
71
|
+
const validator = new Validator();
|
|
72
|
+
|
|
73
|
+
// 1. 验证用户输入
|
|
74
|
+
const userData = {
|
|
75
|
+
email: 'test@example.com',
|
|
76
|
+
age: 25,
|
|
77
|
+
username: 'john'
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const rules = {
|
|
81
|
+
email: { name: '邮箱', type: 'string', regexp: '@email' },
|
|
82
|
+
age: { name: '年龄', type: 'number', min: 0, max: 150 },
|
|
83
|
+
username: { name: '用户名', type: 'string', min: 2, max: 20 }
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const validationResult = validator.validate(userData, rules, ['email', 'username']);
|
|
87
|
+
expect(validationResult.code).toBe(0);
|
|
88
|
+
|
|
89
|
+
// 2. 验证通过后构建 SQL 查询
|
|
90
|
+
const builder = new SqlBuilder();
|
|
91
|
+
const sqlResult = builder.select(['id', 'username', 'email']).from('users').where({ email: userData.email }).toSelectSql();
|
|
92
|
+
|
|
93
|
+
expect(sqlResult.sql).toContain('SELECT');
|
|
94
|
+
expect(sqlResult.sql).toContain('FROM `users`');
|
|
95
|
+
expect(sqlResult.sql).toContain('WHERE `email` = ?');
|
|
96
|
+
expect(sqlResult.params).toContain('test@example.com');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('数据插入:验证 + 字段转换 + SQL 构建', () => {
|
|
100
|
+
const validator = new Validator();
|
|
101
|
+
|
|
102
|
+
// 1. 验证数据
|
|
103
|
+
const newUser = {
|
|
104
|
+
userName: 'jane',
|
|
105
|
+
userEmail: 'jane@example.com',
|
|
106
|
+
userAge: 30
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const rules = {
|
|
110
|
+
userName: { name: '用户名', type: 'string', min: 2, max: 20 },
|
|
111
|
+
userEmail: { name: '邮箱', type: 'string', regexp: '@email' },
|
|
112
|
+
userAge: { name: '年龄', type: 'number', min: 0, max: 150 }
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const validationResult = validator.validate(newUser, rules, ['userName', 'userEmail']);
|
|
116
|
+
expect(validationResult.code).toBe(0);
|
|
117
|
+
|
|
118
|
+
// 2. 字段转换(驼峰转下划线)
|
|
119
|
+
const dbData = keysToSnake(newUser);
|
|
120
|
+
expect(dbData.user_name).toBe('jane');
|
|
121
|
+
expect(dbData.user_email).toBe('jane@example.com');
|
|
122
|
+
expect(dbData.user_age).toBe(30);
|
|
123
|
+
|
|
124
|
+
// 3. 构建插入 SQL
|
|
125
|
+
const builder = new SqlBuilder();
|
|
126
|
+
const sqlResult = builder.toInsertSql('users', dbData);
|
|
127
|
+
|
|
128
|
+
expect(sqlResult.sql).toContain('INSERT INTO `users`');
|
|
129
|
+
expect(sqlResult.params).toContain('jane');
|
|
130
|
+
expect(sqlResult.params).toContain('jane@example.com');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('Integration - XML 解析 + 数据转换', () => {
|
|
135
|
+
test('XML API 响应:解析 + 字段转换', () => {
|
|
136
|
+
const xml = new Xml();
|
|
137
|
+
|
|
138
|
+
// 1. 解析 XML 响应
|
|
139
|
+
const xmlData = '<response><user_id>123</user_id><user_name>John</user_name><is_active>true</is_active></response>';
|
|
140
|
+
const parsed = xml.parse(xmlData) as any;
|
|
141
|
+
|
|
142
|
+
expect(parsed.user_id).toBe(123);
|
|
143
|
+
expect(parsed.user_name).toBe('John');
|
|
144
|
+
expect(parsed.is_active).toBe(true);
|
|
145
|
+
|
|
146
|
+
// 2. 字段转换(下划线转驼峰)
|
|
147
|
+
const camelData = keysToCamel(parsed);
|
|
148
|
+
expect(camelData.userId).toBe(123);
|
|
149
|
+
expect(camelData.userName).toBe('John');
|
|
150
|
+
expect(camelData.isActive).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('Integration - 加密 + JWT + 哈希', () => {
|
|
155
|
+
test('安全令牌生成:多层加密', () => {
|
|
156
|
+
// 1. 生成随机字符串作为 secret
|
|
157
|
+
const secret = Cipher.randomString(32);
|
|
158
|
+
expect(secret.length).toBe(32);
|
|
159
|
+
|
|
160
|
+
// 2. 对 secret 进行 SHA256 哈希
|
|
161
|
+
const hashedSecret = Cipher.sha256(secret);
|
|
162
|
+
expect(hashedSecret.length).toBe(64);
|
|
163
|
+
|
|
164
|
+
// 3. 使用 HMAC 签名
|
|
165
|
+
const data = 'user123:session456';
|
|
166
|
+
const signature = Cipher.hmacSha256(data, hashedSecret);
|
|
167
|
+
expect(signature).toBeDefined();
|
|
168
|
+
|
|
169
|
+
// 4. Base64 编码
|
|
170
|
+
const encoded = Cipher.base64Encode(signature);
|
|
171
|
+
expect(encoded).toBeDefined();
|
|
172
|
+
|
|
173
|
+
// 5. Base64 解码验证
|
|
174
|
+
const decoded = Cipher.base64Decode(encoded);
|
|
175
|
+
expect(decoded).toBe(signature);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('Integration - SQL 构建 + 字段转换', () => {
|
|
180
|
+
test('复杂查询:驼峰转下划线 + WHERE 条件 + 排序分页', () => {
|
|
181
|
+
const builder = new SqlBuilder();
|
|
182
|
+
|
|
183
|
+
// 1. 原始查询条件(驼峰格式)
|
|
184
|
+
const queryConditions = {
|
|
185
|
+
userId: 123,
|
|
186
|
+
userStatus: 'active',
|
|
187
|
+
createdAt: { $gte: 1609459200000 }
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// 2. 转换为下划线格式
|
|
191
|
+
const dbConditions = keysToSnake(queryConditions);
|
|
192
|
+
|
|
193
|
+
// 3. 构建复杂查询
|
|
194
|
+
const sqlResult = builder.select(['id', 'user_name', 'user_email', 'created_at']).from('users').where(dbConditions).orderBy(['created_at#DESC']).limit(20).offset(0).toSelectSql();
|
|
195
|
+
|
|
196
|
+
expect(sqlResult.sql).toContain('SELECT');
|
|
197
|
+
expect(sqlResult.sql).toContain('WHERE');
|
|
198
|
+
expect(sqlResult.sql).toContain('ORDER BY');
|
|
199
|
+
expect(sqlResult.sql).toContain('LIMIT 20');
|
|
200
|
+
expect(sqlResult.params.length).toBeGreaterThan(0);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { describe, test, expect, beforeAll } from 'bun:test';
|
|
2
|
+
import { Jwt } from '../lib/jwt';
|
|
3
|
+
|
|
4
|
+
beforeAll(() => {
|
|
5
|
+
Jwt.configure({
|
|
6
|
+
secret: 'test-secret',
|
|
7
|
+
algorithm: 'HS256',
|
|
8
|
+
expiresIn: '7d'
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('JWT - 签名和验证', () => {
|
|
13
|
+
test('签名创建 token', async () => {
|
|
14
|
+
const token = await Jwt.sign({ userId: 1, name: 'test' });
|
|
15
|
+
expect(typeof token).toBe('string');
|
|
16
|
+
expect(token.split('.').length).toBe(3);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('验证有效 token', async () => {
|
|
20
|
+
const token = await Jwt.sign({ userId: 123, email: 'test@test.com' });
|
|
21
|
+
const decoded = await Jwt.verify(token);
|
|
22
|
+
expect(decoded.userId).toBe(123);
|
|
23
|
+
expect(decoded.email).toBe('test@test.com');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('验证过期 token 抛出错误', async () => {
|
|
27
|
+
const token = await Jwt.sign({ userId: 1 }, { expiresIn: '-1s' });
|
|
28
|
+
expect(Jwt.verify(token)).rejects.toThrow('Token 已过期');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('忽略过期验证', async () => {
|
|
32
|
+
const token = await Jwt.sign({ userId: 1 }, { expiresIn: '-1s' });
|
|
33
|
+
const decoded = await Jwt.verify(token, { ignoreExpiration: true });
|
|
34
|
+
expect(decoded.userId).toBe(1);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('自定义密钥', async () => {
|
|
38
|
+
const secret = 'custom-secret';
|
|
39
|
+
const token = await Jwt.sign({ userId: 1 }, { secret });
|
|
40
|
+
const decoded = await Jwt.verify(token, { secret });
|
|
41
|
+
expect(decoded.userId).toBe(1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('密钥不匹配验证失败', async () => {
|
|
45
|
+
const token = await Jwt.sign({ userId: 1 }, { secret: 'secret1' });
|
|
46
|
+
expect(Jwt.verify(token, { secret: 'secret2' })).rejects.toThrow('Token 签名无效');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('JWT - 解码', () => {
|
|
51
|
+
test('解码不验证签名', async () => {
|
|
52
|
+
const token = await Jwt.sign({ userId: 456 });
|
|
53
|
+
const decoded = Jwt.decode(token);
|
|
54
|
+
expect(decoded.userId).toBe(456);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('解码完整信息', async () => {
|
|
58
|
+
const token = await Jwt.sign({ userId: 1 });
|
|
59
|
+
const decoded = Jwt.decode(token, true);
|
|
60
|
+
expect(decoded.header).toBeDefined();
|
|
61
|
+
expect(decoded.payload).toBeDefined();
|
|
62
|
+
expect(decoded.signature).toBeDefined();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('JWT - 时间相关', () => {
|
|
67
|
+
test('获取剩余时间', async () => {
|
|
68
|
+
const token = await Jwt.sign({ userId: 1 }, { expiresIn: '1h' });
|
|
69
|
+
const remaining = Jwt.getTimeToExpiry(token);
|
|
70
|
+
expect(remaining).toBeGreaterThan(3500);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('检查是否过期', async () => {
|
|
74
|
+
const valid = await Jwt.sign({ userId: 1 }, { expiresIn: '1h' });
|
|
75
|
+
expect(Jwt.isExpired(valid)).toBe(false);
|
|
76
|
+
|
|
77
|
+
const expired = await Jwt.sign({ userId: 1 }, { expiresIn: '-1s' });
|
|
78
|
+
expect(Jwt.isExpired(expired)).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('检查即将过期', async () => {
|
|
82
|
+
const longToken = await Jwt.sign({ userId: 1 }, { expiresIn: '1h' });
|
|
83
|
+
expect(Jwt.isNearExpiry(longToken, 300)).toBe(false);
|
|
84
|
+
|
|
85
|
+
const shortToken = await Jwt.sign({ userId: 1 }, { expiresIn: '2m' });
|
|
86
|
+
expect(Jwt.isNearExpiry(shortToken, 300)).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('JWT - 权限检查', () => {
|
|
91
|
+
test('检查角色', () => {
|
|
92
|
+
const payload = { userId: 1, role: 'admin' };
|
|
93
|
+
expect(Jwt.hasRole(payload, 'admin')).toBe(true);
|
|
94
|
+
expect(Jwt.hasRole(payload, 'user')).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('检查权限', () => {
|
|
98
|
+
const payload = { userId: 1, permissions: ['read', 'write'] };
|
|
99
|
+
expect(Jwt.hasPermission(payload, 'read')).toBe(true);
|
|
100
|
+
expect(Jwt.hasPermission(payload, 'delete')).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('检查所有权限', () => {
|
|
104
|
+
const payload = { userId: 1, permissions: ['read', 'write', 'delete'] };
|
|
105
|
+
expect(Jwt.hasAllPermissions(payload, ['read', 'write'])).toBe(true);
|
|
106
|
+
expect(Jwt.hasAllPermissions(payload, ['read', 'admin'])).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('JWT - 快捷方法', () => {
|
|
111
|
+
test('create 和 check', async () => {
|
|
112
|
+
const token = await Jwt.create({ userId: 789 });
|
|
113
|
+
const decoded = await Jwt.check(token);
|
|
114
|
+
expect(decoded.userId).toBe(789);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('parse 方法', async () => {
|
|
118
|
+
const token = await Jwt.create({ userId: 1 });
|
|
119
|
+
const decoded = Jwt.parse(token);
|
|
120
|
+
expect(decoded.userId).toBe(1);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
|
2
|
+
import { Logger } from '../lib/logger';
|
|
3
|
+
import { existsSync, mkdirSync, rmSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
const testLogDir = join(process.cwd(), 'temp', 'test-logs');
|
|
7
|
+
|
|
8
|
+
beforeAll(() => {
|
|
9
|
+
if (!existsSync(testLogDir)) {
|
|
10
|
+
mkdirSync(testLogDir, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
Logger.configure({
|
|
13
|
+
dir: testLogDir,
|
|
14
|
+
console: 0,
|
|
15
|
+
debug: 1
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterAll(() => {
|
|
20
|
+
if (existsSync(testLogDir)) {
|
|
21
|
+
rmSync(testLogDir, { recursive: true, force: true });
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('Logger - 基本功能', () => {
|
|
26
|
+
test('记录 info 日志', async () => {
|
|
27
|
+
await Logger.log('info', 'Test info message');
|
|
28
|
+
expect(true).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('记录 warn 日志', async () => {
|
|
32
|
+
await Logger.log('warn', 'Test warning');
|
|
33
|
+
expect(true).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('记录 error 日志', async () => {
|
|
37
|
+
await Logger.log('error', 'Test error');
|
|
38
|
+
expect(true).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('记录 debug 日志', async () => {
|
|
42
|
+
await Logger.log('debug', 'Test debug');
|
|
43
|
+
expect(true).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('记录 success 日志', async () => {
|
|
47
|
+
await Logger.success('Test success');
|
|
48
|
+
expect(true).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('Logger - 不同类型消息', () => {
|
|
53
|
+
test('记录字符串', async () => {
|
|
54
|
+
await Logger.log('info', 'String message');
|
|
55
|
+
expect(true).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('记录数字', async () => {
|
|
59
|
+
await Logger.log('info', 12345);
|
|
60
|
+
expect(true).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('记录对象', async () => {
|
|
64
|
+
await Logger.log('info', { userId: 1, action: 'test' });
|
|
65
|
+
expect(true).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('记录数组', async () => {
|
|
69
|
+
await Logger.log('info', ['item1', 'item2']);
|
|
70
|
+
expect(true).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('Logger - 便捷方法', () => {
|
|
75
|
+
test('info 方法', () => {
|
|
76
|
+
Logger.info('Info test');
|
|
77
|
+
expect(true).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('warn 方法', () => {
|
|
81
|
+
Logger.warn('Warn test');
|
|
82
|
+
expect(true).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('error 方法', () => {
|
|
86
|
+
Logger.error('Error test');
|
|
87
|
+
expect(true).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('debug 方法', () => {
|
|
91
|
+
Logger.debug('Debug test');
|
|
92
|
+
expect(true).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
});
|