/** * 管理员功能完整性测试脚本(Cookie + CSRF 认证模型) * * 覆盖范围: * 1. 鉴权与权限校验 * 2. 用户管理(列表、封禁/删除自保护、存储权限) * 3. 系统设置(获取/更新/参数校验) * 4. 分享管理 * 5. 系统监控(健康、存储、日志) * 6. 上传工具接口 */ const http = require('http'); const https = require('https'); const { UserDB } = require('./database'); const BASE_URL = process.env.TEST_BASE_URL || 'http://127.0.0.1:40001'; const state = { adminSession: { cookies: {}, csrfToken: '' }, adminUserId: null, testUserId: null, latestSettings: null }; const testResults = { passed: [], failed: [], warnings: [] }; function makeCookieHeader(cookies) { return Object.entries(cookies || {}) .map(([k, v]) => `${k}=${v}`) .join('; '); } function storeSetCookies(session, setCookieHeader) { if (!session || !setCookieHeader) return; const list = Array.isArray(setCookieHeader) ? setCookieHeader : [setCookieHeader]; for (const raw of list) { const first = String(raw || '').split(';')[0]; const idx = first.indexOf('='); if (idx <= 0) continue; const key = first.slice(0, idx).trim(); const value = first.slice(idx + 1).trim(); session.cookies[key] = value; if (key === 'csrf_token') { session.csrfToken = value; } } } function isSafeMethod(method) { const upper = String(method || '').toUpperCase(); return upper === 'GET' || upper === 'HEAD' || upper === 'OPTIONS'; } function request(method, path, options = {}) { const { data = null, session = null, headers = {}, requireCsrf = true } = options; return new Promise((resolve, reject) => { const url = new URL(path, BASE_URL); const transport = url.protocol === 'https:' ? https : http; const requestHeaders = { ...headers }; if (session) { const cookieHeader = makeCookieHeader(session.cookies); if (cookieHeader) { requestHeaders.Cookie = cookieHeader; } if (requireCsrf && !isSafeMethod(method)) { const csrfToken = session.csrfToken || session.cookies.csrf_token; if (csrfToken) { requestHeaders['X-CSRF-Token'] = csrfToken; } } } let payload = null; if (data !== null && data !== undefined) { payload = JSON.stringify(data); requestHeaders['Content-Type'] = 'application/json'; requestHeaders['Content-Length'] = Buffer.byteLength(payload); } const req = transport.request({ protocol: url.protocol, hostname: url.hostname, port: url.port || (url.protocol === 'https:' ? 443 : 80), path: url.pathname + url.search, method, headers: requestHeaders }, (res) => { let body = ''; res.on('data', chunk => { body += chunk; }); res.on('end', () => { if (session) { storeSetCookies(session, res.headers['set-cookie']); } let parsed = body; try { parsed = body ? JSON.parse(body) : {}; } catch (e) { // keep raw text } resolve({ status: res.statusCode, data: parsed, headers: res.headers }); }); }); req.on('error', reject); if (payload) { req.write(payload); } req.end(); }); } async function initCsrf(session) { const res = await request('GET', '/api/csrf-token', { session, requireCsrf: false }); if (res.status === 200 && res.data && res.data.csrfToken) { session.csrfToken = res.data.csrfToken; } return res; } function assert(condition, message) { if (!condition) { throw new Error(message); } } function warn(message) { testResults.warnings.push(message); console.log(`[WARN] ${message}`); } 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 ensureTestUser() { if (state.testUserId) { return; } const suffix = Date.now(); const username = `admin_test_user_${suffix}`; const email = `${username}@test.local`; const password = `AdminTest#${suffix}`; const id = UserDB.create({ username, email, password, is_verified: 1 }); UserDB.update(id, { is_active: 1, is_banned: 0, storage_permission: 'user_choice', current_storage_type: 'oss' }); state.testUserId = id; } function cleanupTestUser() { if (!state.testUserId) return; try { UserDB.delete(state.testUserId); } catch (error) { warn(`清理测试用户失败: ${error.message}`); } finally { state.testUserId = null; } } // 1. 安全检查:未认证访问应被拒绝 async function testUnauthorizedAccess() { const res = await request('GET', '/api/admin/users'); assert(res.status === 401, `未认证访问应返回401,实际返回: ${res.status}`); } // 2. 安全检查:无效 Token 应被拒绝 async function testInvalidTokenAccess() { const res = await request('GET', '/api/admin/users', { headers: { Authorization: 'Bearer invalid-token' } }); assert(res.status === 401, `无效token应返回401,实际: ${res.status}`); } // 3. 管理员登录(基于 Cookie) async function testAdminLogin() { await initCsrf(state.adminSession); const res = await request('POST', '/api/login', { data: { username: 'admin', password: 'admin123' }, session: state.adminSession, requireCsrf: false }); assert(res.status === 200, `登录应返回200,实际: ${res.status}`); assert(res.data && res.data.success === true, `登录失败: ${res.data?.message || 'unknown'}`); assert(!!state.adminSession.cookies.token, '登录后应写入 token Cookie'); await initCsrf(state.adminSession); const profileRes = await request('GET', '/api/user/profile', { session: state.adminSession }); assert(profileRes.status === 200, `读取profile应返回200,实际: ${profileRes.status}`); assert(profileRes.data?.success === true, '读取profile应成功'); assert(profileRes.data?.user?.is_admin === 1, '登录账号应为管理员'); state.adminUserId = profileRes.data.user.id; assert(Number.isInteger(state.adminUserId) && state.adminUserId > 0, '应获取管理员ID'); } // 4. 用户列表获取(分页) async function testGetUsers() { const res = await request('GET', '/api/admin/users?paged=1&page=1&pageSize=20&sort=created_desc', { session: state.adminSession }); assert(res.status === 200, `应返回200,实际: ${res.status}`); assert(res.data?.success === true, '应返回success: true'); assert(Array.isArray(res.data?.users), 'users应为数组'); assert(!!res.data?.pagination, '分页模式应返回pagination'); } // 5. 系统设置获取 async function testGetSettings() { const res = await request('GET', '/api/admin/settings', { session: state.adminSession }); assert(res.status === 200, `应返回200,实际: ${res.status}`); assert(res.data?.success === true, '应返回success: true'); assert(res.data?.settings && typeof res.data.settings === 'object', '应包含settings对象'); assert(res.data?.settings?.smtp && typeof res.data.settings.smtp === 'object', '应包含smtp配置'); assert(res.data?.settings?.global_theme !== undefined, '应包含全局主题设置'); state.latestSettings = res.data.settings; } // 6. 更新系统设置(写回当前值,避免影响测试环境) async function testUpdateSettings() { const current = state.latestSettings || {}; const payload = { global_theme: current.global_theme || 'dark', max_upload_size: Number(current.max_upload_size || 10737418240) }; const res = await request('POST', '/api/admin/settings', { data: payload, session: state.adminSession }); assert(res.status === 200, `应返回200,实际: ${res.status}`); assert(res.data?.success === true, '应返回success: true'); } // 7. 健康检查 async function testHealthCheck() { const res = await request('GET', '/api/admin/health-check', { session: state.adminSession }); assert(res.status === 200, `应返回200,实际: ${res.status}`); assert(res.data?.success === true, '应返回success: true'); assert(Array.isArray(res.data?.checks), '应包含checks数组'); assert(res.data?.summary && typeof res.data.summary === 'object', '应包含summary'); } // 8. 存储统计 async function testStorageStats() { const res = await request('GET', '/api/admin/storage-stats', { session: state.adminSession }); assert(res.status === 200, `应返回200,实际: ${res.status}`); assert(res.data?.success === true, '应返回success: true'); assert(res.data?.stats && typeof res.data.stats === 'object', '应包含stats对象'); } // 9. 系统日志获取 async function testGetLogs() { const res = await request('GET', '/api/admin/logs?page=1&pageSize=10', { session: state.adminSession }); assert(res.status === 200, `应返回200,实际: ${res.status}`); assert(res.data?.success === true, '应返回success: true'); assert(Array.isArray(res.data?.logs), 'logs应为数组'); } // 10. 日志统计 async function testLogStats() { const res = await request('GET', '/api/admin/logs/stats', { session: state.adminSession }); assert(res.status === 200, `应返回200,实际: ${res.status}`); assert(res.data?.success === true, '应返回success: true'); assert(res.data?.stats && typeof res.data.stats === 'object', '应包含stats对象'); } // 11. 分享列表获取 async function testGetShares() { const res = await request('GET', '/api/admin/shares', { session: state.adminSession }); assert(res.status === 200, `应返回200,实际: ${res.status}`); assert(res.data?.success === true, '应返回success: true'); assert(Array.isArray(res.data?.shares), 'shares应为数组'); } // 12. 不能封禁自己 async function testCannotBanSelf() { assert(state.adminUserId, '管理员ID未初始化'); const res = await request('POST', `/api/admin/users/${state.adminUserId}/ban`, { data: { banned: true }, session: state.adminSession }); assert(res.status === 400, `封禁自己应返回400,实际: ${res.status}`); assert(String(res.data?.message || '').includes('不能封禁自己'), '应提示不能封禁自己'); } // 13. 不能删除自己 async function testCannotDeleteSelf() { assert(state.adminUserId, '管理员ID未初始化'); const res = await request('DELETE', `/api/admin/users/${state.adminUserId}`, { session: state.adminSession }); assert(res.status === 400, `删除自己应返回400,实际: ${res.status}`); assert(String(res.data?.message || '').includes('不能删除自己'), '应提示不能删除自己'); } // 14. 参数验证:无效用户ID async function testInvalidUserId() { const res = await request('POST', '/api/admin/users/invalid/ban', { data: { banned: true }, session: state.adminSession }); assert(res.status === 400, `无效用户ID应返回400,实际: ${res.status}`); } // 15. 参数验证:无效分享ID async function testInvalidShareId() { const res = await request('DELETE', '/api/admin/shares/invalid', { session: state.adminSession }); assert(res.status === 400, `无效分享ID应返回400,实际: ${res.status}`); } // 16. 存储权限设置 async function testSetStoragePermission() { ensureTestUser(); const res = await request('POST', `/api/admin/users/${state.testUserId}/storage-permission`, { data: { storage_permission: 'local_only', local_storage_quota: 2147483648, download_traffic_quota: 3221225472 }, session: state.adminSession }); assert(res.status === 200, `应返回200,实际: ${res.status}`); assert(res.data?.success === true, '应返回success: true'); } // 17. 参数验证:无效的存储权限值 async function testInvalidStoragePermission() { ensureTestUser(); const res = await request('POST', `/api/admin/users/${state.testUserId}/storage-permission`, { data: { storage_permission: 'invalid_permission' }, session: state.adminSession }); assert(res.status === 400, `无效存储权限应返回400,实际: ${res.status}`); } // 18. 主题设置验证 async function testInvalidTheme() { const res = await request('POST', '/api/admin/settings', { data: { global_theme: 'invalid_theme' }, session: state.adminSession }); assert(res.status === 400, `无效主题应返回400,实际: ${res.status}`); } // 19. 日志清理测试 async function testLogCleanup() { const res = await request('POST', '/api/admin/logs/cleanup', { data: { keepDays: 90 }, session: state.adminSession }); assert(res.status === 200, `应返回200,实际: ${res.status}`); assert(res.data?.success === true, '应返回success: true'); assert(typeof res.data?.deletedCount === 'number', 'deletedCount应为数字'); } // 20. SMTP测试(未配置时返回400,配置后可能200/500) async function testSmtpTest() { const res = await request('POST', '/api/admin/settings/test-smtp', { data: { to: 'test@example.com' }, session: state.adminSession }); if (res.status === 400 && String(res.data?.message || '').includes('SMTP未配置')) { console.log(' - SMTP未配置,这是预期的'); return; } assert(res.status === 200 || res.status === 500, `应返回200或500,实际: ${res.status}`); } // 21. 上传工具配置生成(替代已移除的 /api/admin/check-upload-tool) async function testGenerateUploadToolConfig() { const res = await request('POST', '/api/upload/generate-tool', { data: {}, session: state.adminSession }); assert(res.status === 200, `应返回200,实际: ${res.status}`); assert(res.data?.success === true, '应返回success: true'); assert(res.data?.config && typeof res.data.config === 'object', '应包含config对象'); assert(typeof res.data?.config?.api_key === 'string' && res.data.config.api_key.length > 0, '应返回有效api_key'); } // 22. 用户文件查看 - 无效用户ID验证 async function testInvalidUserIdForFiles() { const res = await request('GET', '/api/admin/users/invalid/files', { session: state.adminSession }); assert(res.status === 400, `无效用户ID应返回400,实际: ${res.status}`); } // 23. 删除用户 - 无效用户ID验证 async function testInvalidUserIdForDelete() { const res = await request('DELETE', '/api/admin/users/invalid', { session: state.adminSession }); assert(res.status === 400, `无效用户ID应返回400,实际: ${res.status}`); } // 24. 存储权限设置 - 无效用户ID验证 async function testInvalidUserIdForPermission() { const res = await request('POST', '/api/admin/users/invalid/storage-permission', { data: { storage_permission: 'local_only' }, session: state.adminSession }); assert(res.status === 400, `无效用户ID应返回400,实际: ${res.status}`); } async function runTests() { console.log('========================================'); console.log('管理员功能完整性测试(Cookie + CSRF)'); console.log('========================================\n'); console.log('\n--- 安全检查 ---'); await test('未认证访问应被拒绝', testUnauthorizedAccess); await test('无效token应被拒绝', testInvalidTokenAccess); 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('上传工具配置生成', testGenerateUploadToolConfig); console.log('\n--- 参数验证增强 ---'); await test('用户文件查看无效ID验证', testInvalidUserIdForFiles); await test('删除用户无效ID验证', testInvalidUserIdForDelete); await test('存储权限设置无效ID验证', testInvalidUserIdForPermission); cleanupTestUser(); 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失败的测试:'); for (const f of testResults.failed) { console.log(` - ${f.name}: ${f.error}`); } } if (testResults.warnings.length > 0) { console.log('\n警告:'); for (const w of testResults.warnings) { console.log(` - ${w}`); } } console.log('\n========================================'); process.exit(testResults.failed.length > 0 ? 1 : 0); } runTests().catch((error) => { cleanupTestUser(); console.error('测试执行异常:', error); process.exit(1); });