#!/usr/bin/env node const http = require('http'); class APITester { constructor(baseUrl) { this.baseUrl = baseUrl; this.cookies = {}; this.results = []; } async request(path, options = {}) { return new Promise((resolve, reject) => { const url = new URL(path, this.baseUrl); const opts = { hostname: url.hostname, port: url.port || 3000, path: url.pathname + url.search, method: options.method || 'GET', headers: { 'Content-Type': 'application/json', ...options.headers } }; // 添加 cookies const cookieStr = Object.entries(this.cookies) .map(([k, v]) => `${k}=${v}`) .join('; '); if (cookieStr) { opts.headers.Cookie = cookieStr; } const req = http.request(opts, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { // 保存 cookies if (res.headers['set-cookie']) { res.headers['set-cookie'].forEach(cookie => { const [nameValue] = cookie.split(';'); const [name, value] = nameValue.split('='); if (name && value) { this.cookies[name.trim()] = value.trim(); } }); } let parsedData; try { parsedData = JSON.parse(data); } catch { parsedData = data; } resolve({ status: res.statusCode, data: parsedData, headers: res.headers }); }); }); req.on('error', reject); if (options.body) { req.write(JSON.stringify(options.body)); } req.end(); }); } log(emoji, title, status, message, data = null) { const result = { emoji, title, status, message, data }; this.results.push(result); console.log(`\n${emoji} ${title}`); console.log(` 状态: ${status}`); console.log(` ${message}`); if (data) { console.log(` 数据:`, JSON.stringify(data, null, 2).split('\n').map(l => ' ' + l).join('\n')); } } async testRegister() { try { const email = `test-${Date.now()}@example.com`; const response = await this.request('/api/auth/register', { method: 'POST', body: { email, password: 'password123', name: '测试用户' } }); if (response.status === 200) { this.log('✅', '用户注册', 'PASS', `成功注册用户: ${email}`, response.data); this.testEmail = email; return true; } else { this.log('❌', '用户注册', 'FAIL', `注册失败: ${response.status}`, response.data); return false; } } catch (error) { this.log('❌', '用户注册', 'ERROR', error.message); return false; } } async testGetCSRF() { try { const response = await this.request('/api/auth/csrf', { method: 'GET' }); if (response.status === 200 && response.data.csrfToken) { this.csrfToken = response.data.csrfToken; this.log('✅', '获取 CSRF Token', 'PASS', 'CSRF Token 获取成功'); return true; } else { this.log('❌', '获取 CSRF Token', 'FAIL', `状态码: ${response.status}`); return false; } } catch (error) { this.log('❌', '获取 CSRF Token', 'ERROR', error.message); return false; } } async testLogin() { try { const response = await this.request('/api/auth/callback/credentials', { method: 'POST', body: { email: this.testEmail, password: 'password123', csrfToken: this.csrfToken, callbackUrl: 'http://localhost:3000', json: true } }); if (response.status === 200 || response.status === 302) { this.log('✅', '用户登录', 'PASS', '登录成功,获取到 session'); return true; } else { this.log('❌', '用户登录', 'FAIL', `状态码: ${response.status}`, response.data); return false; } } catch (error) { this.log('❌', '用户登录', 'ERROR', error.message); return false; } } async testGetTrees() { try { const response = await this.request('/api/trees', { method: 'GET' }); if (response.status === 200) { this.log('✅', '获取家族树列表', 'PASS', `成功获取 ${response.data.trees?.length || 0} 个家族树`, response.data); return true; } else if (response.status === 401) { this.log('⚠️', '获取家族树列表', 'SKIP', '需要登录(权限保护正常)', response.data); return false; } else { this.log('❌', '获取家族树列表', 'FAIL', `状态码: ${response.status}`, response.data); return false; } } catch (error) { this.log('❌', '获取家族树列表', 'ERROR', error.message); return false; } } async testCreateTree() { try { const response = await this.request('/api/trees', { method: 'POST', body: { name: '测试家族树', description: 'API 自动化测试创建' } }); if (response.status === 201) { this.treeId = response.data.tree.id; this.log('✅', '创建家族树', 'PASS', `成功创建家族树: ${this.treeId}`, response.data); return true; } else if (response.status === 401) { this.log('⚠️', '创建家族树', 'SKIP', '需要登录(权限保护正常)', response.data); return false; } else { this.log('❌', '创建家族树', 'FAIL', `状态码: ${response.status}`, response.data); return false; } } catch (error) { this.log('❌', '创建家族树', 'ERROR', error.message); return false; } } async testGetMembers() { if (!this.treeId) { this.log('⚠️', '获取成员列表', 'SKIP', '没有可用的家族树 ID'); return false; } try { const response = await this.request(`/api/trees/${this.treeId}/members`, { method: 'GET' }); if (response.status === 200) { this.log('✅', '获取成员列表', 'PASS', `成功获取 ${response.data.members?.length || 0} 个成员`, response.data); return true; } else if (response.status === 401 || response.status === 403) { this.log('⚠️', '获取成员列表', 'SKIP', '需要权限', response.data); return false; } else { this.log('❌', '获取成员列表', 'FAIL', `状态码: ${response.status}`, response.data); return false; } } catch (error) { this.log('❌', '获取成员列表', 'ERROR', error.message); return false; } } async testCreateMember() { if (!this.treeId) { this.log('⚠️', '创建成员', 'SKIP', '没有可用的家族树 ID'); return false; } try { const response = await this.request(`/api/trees/${this.treeId}/members`, { method: 'POST', body: { surname: '李', givenName: '明', fullName: '李明', gender: 'MALE', generation: 1, birthDate: '1990-01-01' } }); if (response.status === 201) { this.memberId = response.data.member.id; this.log('✅', '创建成员', 'PASS', `成功创建成员: ${response.data.member.fullName}`, response.data); return true; } else if (response.status === 401 || response.status === 403) { this.log('⚠️', '创建成员', 'SKIP', '需要权限', response.data); return false; } else { this.log('❌', '创建成员', 'FAIL', `状态码: ${response.status}`, response.data); return false; } } catch (error) { this.log('❌', '创建成员', 'ERROR', error.message); return false; } } async testUnauthorizedAccess() { // 清除 cookies 测试未授权访问 const savedCookies = { ...this.cookies }; this.cookies = {}; try { const response = await this.request('/api/trees', { method: 'GET' }); if (response.status === 401) { this.log('✅', '权限验证', 'PASS', '未登录正确返回 401', response.data); this.cookies = savedCookies; return true; } else { this.log('❌', '权限验证', 'FAIL', `应该返回 401,实际: ${response.status}`, response.data); this.cookies = savedCookies; return false; } } catch (error) { this.log('❌', '权限验证', 'ERROR', error.message); this.cookies = savedCookies; return false; } } printSummary() { console.log('\n\n' + '='.repeat(60)); console.log('📊 测试总结'); console.log('='.repeat(60)); const passed = this.results.filter(r => r.status === 'PASS').length; const failed = this.results.filter(r => r.status === 'FAIL').length; const errors = this.results.filter(r => r.status === 'ERROR').length; const skipped = this.results.filter(r => r.status === 'SKIP').length; const total = this.results.length; console.log(`\n总计: ${total} 个测试`); console.log(`✅ 通过: ${passed}`); console.log(`❌ 失败: ${failed}`); console.log(`⚠️ 跳过: ${skipped}`); console.log(`💥 错误: ${errors}`); const successRate = total > 0 ? ((passed / (total - skipped)) * 100).toFixed(1) : 0; console.log(`\n成功率: ${successRate}%`); if (failed === 0 && errors === 0) { console.log('\n🎉 所有测试通过!'); } else { console.log('\n⚠️ 存在失败的测试,请检查上面的详细信息'); } console.log('='.repeat(60) + '\n'); } async runAllTests() { console.log('🚀 开始全面 API 测试...'); console.log('目标: http://localhost:3000'); console.log('='.repeat(60)); // 1. 测试注册 await this.testRegister(); // 2. 测试权限验证(未登录) await this.testUnauthorizedAccess(); // 3. 获取 CSRF Token await this.testGetCSRF(); // 4. 测试登录 await this.testLogin(); // 5. 测试获取家族树列表 await this.testGetTrees(); // 6. 测试创建家族树 await this.testCreateTree(); // 7. 测试获取成员列表 await this.testGetMembers(); // 8. 测试创建成员 await this.testCreateMember(); // 9. 再次获取成员列表(验证创建成功) if (this.memberId) { await this.testGetMembers(); } // 打印总结 this.printSummary(); } } // 运行测试 const tester = new APITester('http://localhost:3000'); tester.runAllTests().catch(console.error);