Files
vue-driven-cloud-storage/backend/test_admin.js
237899745 4350113979 fix: 修复配额说明重复和undefined问题
- 在editStorageForm中初始化oss_storage_quota_value和oss_quota_unit
- 删除重复的旧配额说明块,保留新的当前配额设置显示

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 19:39:53 +08:00

575 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 管理员功能完整性测试脚本
* 测试范围:
* 1. 用户管理 - 用户列表、搜索、封禁/解封、删除、修改存储权限、查看用户文件
* 2. 系统设置 - SMTP邮件配置、存储配置、注册开关、主题设置
* 3. 分享管理 - 查看所有分享、删除分享
* 4. 系统监控 - 健康检查、存储统计、操作日志
* 5. 安全检查 - 管理员权限验证、敏感操作确认
*/
const http = require('http');
const BASE_URL = 'http://localhost:40001';
let adminToken = '';
let testUserId = null;
let testShareId = null;
// 测试结果收集
const testResults = {
passed: [],
failed: [],
warnings: []
};
// 辅助函数发送HTTP请求
function request(method, path, data = null, token = null) {
return new Promise((resolve, reject) => {
const url = new URL(path, BASE_URL);
const options = {
hostname: url.hostname,
port: url.port,
path: url.pathname + url.search,
method: method,
headers: {
'Content-Type': 'application/json'
}
};
if (token) {
options.headers['Authorization'] = `Bearer ${token}`;
}
const req = http.request(options, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
try {
const json = JSON.parse(body);
resolve({ status: res.statusCode, data: json });
} catch (e) {
resolve({ status: res.statusCode, data: body });
}
});
});
req.on('error', reject);
if (data) {
req.write(JSON.stringify(data));
}
req.end();
});
}
// 测试函数包装器
async function test(name, fn) {
try {
await fn();
testResults.passed.push(name);
console.log(`[PASS] ${name}`);
} catch (error) {
testResults.failed.push({ name, error: error.message });
console.log(`[FAIL] ${name}: ${error.message}`);
}
}
// 警告记录
function warn(message) {
testResults.warnings.push(message);
console.log(`[WARN] ${message}`);
}
// 断言函数
function assert(condition, message) {
if (!condition) {
throw new Error(message);
}
}
// ============ 测试用例 ============
// 1. 安全检查:未认证访问应被拒绝
async function testUnauthorizedAccess() {
const res = await request('GET', '/api/admin/users');
assert(res.status === 401, `未认证访问应返回401实际返回: ${res.status}`);
}
// 2. 管理员登录
async function testAdminLogin() {
const res = await request('POST', '/api/login', {
username: 'admin',
password: 'admin123',
captcha: '' // 开发环境可能不需要验证码
});
// 登录可能因为验证码失败,这是预期的
if (res.status === 400 && res.data.message && res.data.message.includes('验证码')) {
warn('登录需要验证码跳过登录测试使用模拟token');
// 使用JWT库生成一个测试token需要知道JWT_SECRET
// 或者直接查询数据库
return;
}
if (res.data.success) {
adminToken = res.data.token;
console.log(' - 获取到管理员token');
} else {
throw new Error(`登录失败: ${res.data.message}`);
}
}
// 3. 用户列表获取
async function testGetUsers() {
if (!adminToken) {
warn('无admin token跳过用户列表测试');
return;
}
const res = await request('GET', '/api/admin/users', null, adminToken);
assert(res.status === 200, `应返回200实际: ${res.status}`);
assert(res.data.success === true, '应返回success: true');
assert(Array.isArray(res.data.users), 'users应为数组');
// 记录测试用户ID
if (res.data.users.length > 1) {
const nonAdminUser = res.data.users.find(u => !u.is_admin);
if (nonAdminUser) {
testUserId = nonAdminUser.id;
}
}
}
// 4. 系统设置获取
async function testGetSettings() {
if (!adminToken) {
warn('无admin token跳过系统设置测试');
return;
}
const res = await request('GET', '/api/admin/settings', null, adminToken);
assert(res.status === 200, `应返回200实际: ${res.status}`);
assert(res.data.success === true, '应返回success: true');
assert(res.data.settings !== undefined, '应包含settings对象');
assert(res.data.settings.smtp !== undefined, '应包含smtp配置');
assert(res.data.settings.global_theme !== undefined, '应包含全局主题设置');
}
// 5. 更新系统设置
async function testUpdateSettings() {
if (!adminToken) {
warn('无admin token跳过更新系统设置测试');
return;
}
const res = await request('POST', '/api/admin/settings', {
global_theme: 'dark',
max_upload_size: 10737418240
}, adminToken);
assert(res.status === 200, `应返回200实际: ${res.status}`);
assert(res.data.success === true, '应返回success: true');
}
// 6. 健康检查
async function testHealthCheck() {
if (!adminToken) {
warn('无admin token跳过健康检查测试');
return;
}
const res = await request('GET', '/api/admin/health-check', null, adminToken);
assert(res.status === 200, `应返回200实际: ${res.status}`);
assert(res.data.success === true, '应返回success: true');
assert(res.data.checks !== undefined, '应包含checks数组');
assert(res.data.overallStatus !== undefined, '应包含overallStatus');
assert(res.data.summary !== undefined, '应包含summary');
// 检查各项检测项目
const checkNames = res.data.checks.map(c => c.name);
assert(checkNames.includes('JWT密钥'), '应包含JWT密钥检查');
assert(checkNames.includes('数据库连接'), '应包含数据库连接检查');
assert(checkNames.includes('存储目录'), '应包含存储目录检查');
}
// 7. 存储统计
async function testStorageStats() {
if (!adminToken) {
warn('无admin token跳过存储统计测试');
return;
}
const res = await request('GET', '/api/admin/storage-stats', null, adminToken);
assert(res.status === 200, `应返回200实际: ${res.status}`);
assert(res.data.success === true, '应返回success: true');
assert(res.data.stats !== undefined, '应包含stats对象');
assert(typeof res.data.stats.totalDisk === 'number', 'totalDisk应为数字');
}
// 8. 系统日志获取
async function testGetLogs() {
if (!adminToken) {
warn('无admin token跳过系统日志测试');
return;
}
const res = await request('GET', '/api/admin/logs?page=1&pageSize=10', null, adminToken);
assert(res.status === 200, `应返回200实际: ${res.status}`);
assert(res.data.success === true, '应返回success: true');
assert(Array.isArray(res.data.logs), 'logs应为数组');
assert(typeof res.data.total === 'number', 'total应为数字');
}
// 9. 日志统计
async function testLogStats() {
if (!adminToken) {
warn('无admin token跳过日志统计测试');
return;
}
const res = await request('GET', '/api/admin/logs/stats', null, adminToken);
assert(res.status === 200, `应返回200实际: ${res.status}`);
assert(res.data.success === true, '应返回success: true');
assert(res.data.stats !== undefined, '应包含stats对象');
}
// 10. 分享列表获取
async function testGetShares() {
if (!adminToken) {
warn('无admin token跳过分享列表测试');
return;
}
const res = await request('GET', '/api/admin/shares', null, adminToken);
assert(res.status === 200, `应返回200实际: ${res.status}`);
assert(res.data.success === true, '应返回success: true');
assert(Array.isArray(res.data.shares), 'shares应为数组');
// 记录测试分享ID
if (res.data.shares.length > 0) {
testShareId = res.data.shares[0].id;
}
}
// 11. 安全检查普通用户不能访问管理员API
async function testNonAdminAccess() {
// 使用一个无效的token模拟普通用户
const fakeToken = 'invalid-token';
const res = await request('GET', '/api/admin/users', null, fakeToken);
assert(res.status === 401, `无效token应返回401实际: ${res.status}`);
}
// 12. 安全检查:不能封禁自己
async function testCannotBanSelf() {
if (!adminToken) {
warn('无admin token跳过封禁自己测试');
return;
}
// 获取当前管理员ID
const usersRes = await request('GET', '/api/admin/users', null, adminToken);
const adminUser = usersRes.data.users.find(u => u.is_admin);
if (!adminUser) {
warn('未找到管理员用户');
return;
}
const res = await request('POST', `/api/admin/users/${adminUser.id}/ban`, {
banned: true
}, adminToken);
assert(res.status === 400, `封禁自己应返回400实际: ${res.status}`);
assert(res.data.message.includes('不能封禁自己'), '应提示不能封禁自己');
}
// 13. 安全检查:不能删除自己
async function testCannotDeleteSelf() {
if (!adminToken) {
warn('无admin token跳过删除自己测试');
return;
}
const usersRes = await request('GET', '/api/admin/users', null, adminToken);
const adminUser = usersRes.data.users.find(u => u.is_admin);
if (!adminUser) {
warn('未找到管理员用户');
return;
}
const res = await request('DELETE', `/api/admin/users/${adminUser.id}`, null, adminToken);
assert(res.status === 400, `删除自己应返回400实际: ${res.status}`);
assert(res.data.message.includes('不能删除自己'), '应提示不能删除自己');
}
// 14. 参数验证无效用户ID
async function testInvalidUserId() {
if (!adminToken) {
warn('无admin token跳过无效用户ID测试');
return;
}
const res = await request('POST', '/api/admin/users/invalid/ban', {
banned: true
}, adminToken);
assert(res.status === 400, `无效用户ID应返回400实际: ${res.status}`);
}
// 15. 参数验证无效分享ID
async function testInvalidShareId() {
if (!adminToken) {
warn('无admin token跳过无效分享ID测试');
return;
}
const res = await request('DELETE', '/api/admin/shares/invalid', null, adminToken);
assert(res.status === 400, `无效分享ID应返回400实际: ${res.status}`);
}
// 16. 存储权限设置
async function testSetStoragePermission() {
if (!adminToken || !testUserId) {
warn('无admin token或测试用户跳过存储权限测试');
return;
}
const res = await request('POST', `/api/admin/users/${testUserId}/storage-permission`, {
storage_permission: 'local_only',
local_storage_quota: 2147483648 // 2GB
}, adminToken);
assert(res.status === 200, `应返回200实际: ${res.status}`);
assert(res.data.success === true, '应返回success: true');
}
// 17. 参数验证:无效的存储权限值
async function testInvalidStoragePermission() {
if (!adminToken || !testUserId) {
warn('无admin token或测试用户跳过无效存储权限测试');
return;
}
const res = await request('POST', `/api/admin/users/${testUserId}/storage-permission`, {
storage_permission: 'invalid_permission'
}, adminToken);
assert(res.status === 400, `无效存储权限应返回400实际: ${res.status}`);
}
// 18. 主题设置验证
async function testInvalidTheme() {
if (!adminToken) {
warn('无admin token跳过无效主题测试');
return;
}
const res = await request('POST', '/api/admin/settings', {
global_theme: 'invalid_theme'
}, adminToken);
assert(res.status === 400, `无效主题应返回400实际: ${res.status}`);
}
// 19. 日志清理测试
async function testLogCleanup() {
if (!adminToken) {
warn('无admin token跳过日志清理测试');
return;
}
const res = await request('POST', '/api/admin/logs/cleanup', {
keepDays: 90
}, adminToken);
assert(res.status === 200, `应返回200实际: ${res.status}`);
assert(res.data.success === true, '应返回success: true');
assert(typeof res.data.deletedCount === 'number', 'deletedCount应为数字');
}
// 20. SMTP测试预期失败因为未配置
async function testSmtpTest() {
if (!adminToken) {
warn('无admin token跳过SMTP测试');
return;
}
const res = await request('POST', '/api/admin/settings/test-smtp', {
to: 'test@example.com'
}, adminToken);
// SMTP未配置时应返回400
if (res.status === 400 && res.data.message && res.data.message.includes('SMTP未配置')) {
console.log(' - SMTP未配置这是预期的');
return;
}
// 如果SMTP已配置可能成功或失败
assert(res.status === 200 || res.status === 500, `应返回200或500实际: ${res.status}`);
}
// 21. 上传工具检查
async function testCheckUploadTool() {
if (!adminToken) {
warn('无admin token跳过上传工具检查测试');
return;
}
const res = await request('GET', '/api/admin/check-upload-tool', null, adminToken);
assert(res.status === 200, `应返回200实际: ${res.status}`);
assert(res.data.success === true, '应返回success: true');
assert(typeof res.data.exists === 'boolean', 'exists应为布尔值');
}
// 22. 用户文件查看 - 无效用户ID验证
async function testInvalidUserIdForFiles() {
if (!adminToken) {
warn('无admin token跳过用户文件查看无效ID测试');
return;
}
const res = await request('GET', '/api/admin/users/invalid/files', null, adminToken);
assert(res.status === 400, `无效用户ID应返回400实际: ${res.status}`);
}
// 23. 删除用户 - 无效用户ID验证
async function testInvalidUserIdForDelete() {
if (!adminToken) {
warn('无admin token跳过删除用户无效ID测试');
return;
}
const res = await request('DELETE', '/api/admin/users/invalid', null, adminToken);
assert(res.status === 400, `无效用户ID应返回400实际: ${res.status}`);
}
// 24. 存储权限设置 - 无效用户ID验证
async function testInvalidUserIdForPermission() {
if (!adminToken) {
warn('无admin token跳过存储权限无效ID测试');
return;
}
const res = await request('POST', '/api/admin/users/invalid/storage-permission', {
storage_permission: 'local_only'
}, adminToken);
assert(res.status === 400, `无效用户ID应返回400实际: ${res.status}`);
}
// 主测试函数
async function runTests() {
console.log('========================================');
console.log('管理员功能完整性测试');
console.log('========================================\n');
// 先尝试直接使用数据库获取token
try {
const jwt = require('jsonwebtoken');
const { UserDB } = require('./database');
require('dotenv').config();
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-in-production';
const adminUser = UserDB.findByUsername('admin');
if (adminUser) {
adminToken = jwt.sign(
{
id: adminUser.id,
username: adminUser.username,
is_admin: adminUser.is_admin,
type: 'access'
},
JWT_SECRET,
{ expiresIn: '2h' }
);
console.log('[INFO] 已通过数据库直接生成管理员token\n');
}
} catch (e) {
console.log('[INFO] 无法直接生成token将尝试登录: ' + e.message + '\n');
}
// 安全检查测试
console.log('\n--- 安全检查 ---');
await test('未认证访问应被拒绝', testUnauthorizedAccess);
await test('无效token应被拒绝', testNonAdminAccess);
// 如果还没有token尝试登录
if (!adminToken) {
await test('管理员登录', testAdminLogin);
}
// 用户管理测试
console.log('\n--- 用户管理 ---');
await test('获取用户列表', testGetUsers);
await test('不能封禁自己', testCannotBanSelf);
await test('不能删除自己', testCannotDeleteSelf);
await test('无效用户ID验证', testInvalidUserId);
await test('设置存储权限', testSetStoragePermission);
await test('无效存储权限验证', testInvalidStoragePermission);
// 系统设置测试
console.log('\n--- 系统设置 ---');
await test('获取系统设置', testGetSettings);
await test('更新系统设置', testUpdateSettings);
await test('无效主题验证', testInvalidTheme);
await test('SMTP测试', testSmtpTest);
// 分享管理测试
console.log('\n--- 分享管理 ---');
await test('获取分享列表', testGetShares);
await test('无效分享ID验证', testInvalidShareId);
// 系统监控测试
console.log('\n--- 系统监控 ---');
await test('健康检查', testHealthCheck);
await test('存储统计', testStorageStats);
await test('获取系统日志', testGetLogs);
await test('日志统计', testLogStats);
await test('日志清理', testLogCleanup);
// 其他功能测试
console.log('\n--- 其他功能 ---');
await test('上传工具检查', testCheckUploadTool);
// 参数验证增强测试
console.log('\n--- 参数验证增强 ---');
await test('用户文件查看无效ID验证', testInvalidUserIdForFiles);
await test('删除用户无效ID验证', testInvalidUserIdForDelete);
await test('存储权限设置无效ID验证', testInvalidUserIdForPermission);
// 输出测试结果
console.log('\n========================================');
console.log('测试结果汇总');
console.log('========================================');
console.log(`通过: ${testResults.passed.length}`);
console.log(`失败: ${testResults.failed.length}`);
console.log(`警告: ${testResults.warnings.length}`);
if (testResults.failed.length > 0) {
console.log('\n失败的测试:');
testResults.failed.forEach(f => {
console.log(` - ${f.name}: ${f.error}`);
});
}
if (testResults.warnings.length > 0) {
console.log('\n警告:');
testResults.warnings.forEach(w => {
console.log(` - ${w}`);
});
}
console.log('\n========================================');
// 返回退出码
process.exit(testResults.failed.length > 0 ? 1 : 0);
}
// 运行测试
runTests().catch(err => {
console.error('测试执行错误:', err);
process.exit(1);
});