// TradeMate 全功能 API 测试 v3 - 修复已知问题 import crypto from 'crypto'; const BASE = 'http://localhost:8000/api/v1'; const TIMEOUT = 30000; const PHONE = `138${crypto.randomInt(10000000, 99999999)}`; const PASSWORD = 'TestPass123!'; const USERNAME = `test_${crypto.randomInt(1000, 9999)}`; let token = null; let productId = null; let customerId = null; const results = { pass: 0, fail: 0, errors: [] }; function log(name, ok, detail = '') { if (ok) { results.pass++; console.log(` ✅ ${name}`); } else { results.fail++; console.log(` ❌ ${name}${detail ? ': ' + detail : ''}`); results.errors.push(`${name}: ${detail}`); } } async function api(path, opts = {}) { const timeout = opts.timeout || TIMEOUT; const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeout); try { const headers = { 'Content-Type': 'application/json' }; if (token && !opts.noAuth) headers['Authorization'] = `Bearer ${token}`; const res = await fetch(`${BASE}${path}`, { signal: controller.signal, method: opts.method || 'GET', headers: opts.headers || headers, body: opts.body ? JSON.stringify(opts.body) : undefined, }); const status = res.status; let data; try { data = await res.json(); } catch { data = null; } return { status, data }; } catch (e) { return { status: 0, data: { detail: e.message || 'Request failed' } }; } finally { clearTimeout(timer); } } function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } // ===== 1. 认证 ===== async function testAuth() { console.log('\n📋 认证模块'); const reg = await api('/auth/register', { method: 'POST', body: { phone: PHONE, password: PASSWORD, username: USERNAME }, noAuth: true }); log('注册新用户', reg.status === 200 && reg.data?.phone === PHONE, reg.data?.detail); const dup = await api('/auth/register', { method: 'POST', body: { phone: PHONE, password: PASSWORD }, noAuth: true }); log('重复注册拒绝', [400, 409].includes(reg.status) || dup.status >= 400); const login = await api('/auth/login', { method: 'POST', body: { username: PHONE, password: PASSWORD }, noAuth: true }); log('手机号登录', login.status === 200 && !!login.data?.access_token, login.data?.detail); if (login.data?.access_token) token = login.data.access_token; const badPass = await api('/auth/login', { method: 'POST', body: { username: PHONE, password: 'wrong' }, noAuth: true }); log('错误密码拒绝', badPass.status === 401); const noPass = await api('/auth/login', { method: 'POST', body: { username: PHONE }, noAuth: true }); log('缺少密码422', noPass.status === 422); const noPhone = await api('/auth/login', { method: 'POST', body: { password: PASSWORD }, noAuth: true }); log('缺少账号422', noPhone.status === 422); const me = await api('/auth/me'); log('获取用户信息', me.status === 200 && me.data?.phone === PHONE, me.data?.detail); const settings = await api('/auth/settings', { method: 'PATCH', body: { reply_tone: 'formal' } }); log('更新用户设置', [200, 204].includes(settings.status)); const guest = await api('/auth/login/guest', { method: 'POST', noAuth: true }); log('游客登录', guest.status === 200 && !!guest.data?.access_token); if (login.data?.refresh_token) { const refresh = await api('/auth/refresh', { method: 'POST', body: { refresh_token: login.data.refresh_token }, noAuth: true }); log('Token 刷新', refresh.status === 200 && !!refresh.data?.access_token); } } // ===== 2. 客户 ===== async function testCustomers() { console.log('\n📋 客户模块'); const list = await api('/customers?page=1&size=20'); log('客户列表', list.status === 200 && Array.isArray(list.data?.items ?? list.data)); const create = await api('/customers', { method: 'POST', body: { name: '测试客户', company: '测试公司', email: 'test@test.com', phone: '13800000001', country: 'US', status: 'lead' } }); log('创建客户', create.status === 200 && !!create.data?.id, create.data?.detail); if (create.data?.id) customerId = create.data.id; if (customerId) { const get = await api(`/customers/${customerId}`); log('获取客户详情', get.status === 200 && get.data?.name === '测试客户', get.data?.detail); const upd = await api(`/customers/${customerId}`, { method: 'PATCH', body: { company: '新公司' } }); log('更新客户', upd.status === 200 && upd.data?.company === '新公司', upd.data?.detail); const health = await api(`/customers/${customerId}/health`); log('客户健康分', health.status === 200 && health.data != null); const del = await api(`/customers/${customerId}`, { method: 'DELETE' }); log('删除客户', [200, 204].includes(del.status)); } const overview = await api('/customers/health-overview'); log('健康分概览', overview.status === 200 && overview.data != null, overview.data?.detail); const silent = await api('/customers/silent?days=3'); log('沉默客户列表', silent.status === 200); } // ===== 3. 产品 ===== async function testProducts() { console.log('\n📋 产品模块'); const list = await api('/products?page=1&size=20'); log('产品列表', list.status === 200); const create = await api('/products', { method: 'POST', body: { name: '测试产品', name_en: 'Test Product', description: '这是一个测试产品', description_en: 'This is a test product', category: 'general', price: 100, price_unit: 'USD', moq: 10 } }); log('创建产品', create.status === 200 && !!create.data?.id, create.data?.detail); if (create.data?.id) productId = create.data.id; if (productId) { const get = await api(`/products/${productId}`); log('获取产品详情', get.status === 200 && get.data?.name === '测试产品'); const upd = await api(`/products/${productId}`, { method: 'PATCH', body: { price: 150 } }); log('更新产品', upd.status === 200 && String(upd.data?.price) === '150', JSON.stringify(upd.data)); const del = await api(`/products/${productId}`, { method: 'DELETE' }); log('删除产品', [200, 204].includes(del.status)); const afterDel = await api(`/products/${productId}`); log('已删产品不可见', afterDel.status === 404 || afterDel.data?.is_active === false, JSON.stringify(afterDel.data)); } } // ===== 4. 营销 ===== async function testMarketing() { console.log('\n📋 营销模块'); console.log(' ⏳ 文案生成中(约15-30秒)...'); const gen = await api('/marketing/generate', { method: 'POST', body: { product_name: '户外折叠椅', description: '便携户外折叠椅,铝合金材质,承重150kg', category: 'outdoor', target: 'US importers', style: 'professional', count: 1, language: 'en' }, timeout: 60000 }); log('营销文案生成', gen.status === 200 && Array.isArray(gen.data?.results), gen.data?.detail || `${gen.data?.results?.length || 0}条`); if (gen.data?.results) gen.data.results.forEach((r, i) => console.log(` [${i}] ${r.style}: ${(r.content || '').slice(0, 60)}...`)); const kw = await api('/marketing/keywords', { method: 'POST', body: { product_name: '户外折叠椅', description: '便携户外折叠椅,铝合金材质', category: 'outdoor' }, timeout: 30000 }); log('关键词生成', kw.status === 200 && Array.isArray(kw.data?.keywords), kw.data?.detail || `${kw.data?.keywords?.length || 0}个`); const comp = await api('/marketing/competitor-analysis', { method: 'POST', body: { product_name: '户外折叠椅', description: '便携户外折叠椅', category: 'outdoor', market: 'US' }, timeout: 30000 }); log('竞品分析', comp.status === 200 && comp.data?.analysis != null, comp.data?.detail); } // ===== 5. 报价 ===== async function testQuotations() { console.log('\n📋 报价模块'); const list = await api('/quotations?page=1&size=20'); log('报价列表', list.status === 200); const create = await api('/quotations', { method: 'POST', body: { customer_name: '测试客户', items: [{ product_name: '测试产品', quantity: 100, unit_price: 50, total_price: 5000 }], total_amount: 5000, currency: 'USD', payment_terms: 'T/T', delivery_terms: 'FOB' } }); log('创建报价', create.status === 200 && !!create.data?.id, create.data?.detail); const qid = create.data?.id; if (qid) { const get = await api(`/quotations/${qid}`); log('获取报价详情', get.status === 200 && get.data?.total_amount == 5000, JSON.stringify(get.data).slice(0, 100)); const updStatus = await api(`/quotations/${qid}/status`, { method: 'PATCH', body: { status: 'sent' } }); log('更新报价状态', [200, 204].includes(updStatus.status), JSON.stringify(updStatus.data)); } } // ===== 6. 翻译 ===== async function testTranslation() { console.log('\n📋 翻译模块'); console.log(' ⏳ AI翻译中...'); const trans = await api('/translate', { method: 'POST', body: { text: 'Hello, how are you?', source_lang: 'en', target_lang: 'zh' }, timeout: 60000 }); log('文本翻译', trans.status === 200 && trans.data?.translated_text != null, trans.data?.detail); const reply = await api('/translate/reply', { method: 'POST', body: { inquiry: 'What is your price?', tone: 'professional', count: 1 }, timeout: 60000 }); log('回复建议', reply.status === 200 && (Array.isArray(reply.data?.replies) || Array.isArray(reply.data?.suggestions)), reply.data?.detail || `${JSON.stringify(reply.data).slice(0, 100)}`); const extract = await api('/translate/extract', { method: 'POST', body: { text: '500 units, $10 each, delivery March' }, timeout: 15000 }); log('信息提取', extract.status === 200 && extract.data != null, extract.data?.detail); } // ===== 7. 引导 ===== async function testOnboarding() { console.log('\n📋 引导模块'); const status = await api('/onboarding/status'); log('引导状态查询', status.status === 200 && status.data != null); console.log(' ⏳ 引导创建产品中...'); const product = await api('/onboarding/product', { method: 'POST', body: { name: '测试引导产品', description: '引导流程中创建的产品', target: 'US importers' }, timeout: 60000 }); log('引导创建产品', product.status === 200 && product.data?.product != null, product.data?.detail || `${JSON.stringify(product.data).slice(0, 100)}`); } // ===== 8. 跟进 ===== async function testFollowup() { console.log('\n📋 跟进模块'); const stats = await api('/followup/stats'); log('跟进统计', stats.status === 200 && stats.data != null); const pending = await api('/followup/pending?page=1&size=20'); log('待跟进列表', pending.status === 200); const logs = await api('/followup/logs?page=1&size=20'); log('跟进记录', logs.status === 200); } // ===== 9. 分析 ===== async function testAnalytics() { console.log('\n📋 分析模块'); const overview = await api('/analytics/overview'); log('分析概览', overview.status === 200 && overview.data != null); const customers = await api('/analytics/customers'); log('客户分析', customers.status === 200); const translations = await api('/analytics/translations'); log('翻译分析', translations.status === 200); } // ===== 10. 通知 ===== async function testNotifications() { console.log('\n📋 通知模块'); const list = await api('/notifications?page=1&size=20'); log('通知列表', list.status === 200); const unread = await api('/notifications/unread-count'); log('未读数', unread.status === 200); } // ===== 11. 反馈 ===== async function testFeedback() { console.log('\n📋 反馈模块'); const fb = await api('/feedback', { method: 'POST', body: { category: 'bug', content: '测试反馈', contact: 'test@test.com' } }); log('提交反馈', [200, 201].includes(fb.status)); } // ===== 12. 汇率 ===== async function testExchange() { console.log('\n📋 汇率模块'); const rates = await api('/exchange/rates?base=USD'); log('获取汇率', rates.status === 200, rates.data?.detail); const convert = await api('/exchange/convert?from=USD&to=CNY&amount=100'); log('货币转换', convert.status === 200 && convert.data?.converted_amount != null, convert.data?.detail || JSON.stringify(convert.data).slice(0, 100)); } // ===== 13. 管理 ===== async function testAdmin() { console.log('\n📋 管理模块'); const health = await api('/admin/health', { noAuth: true }); log('系统健康检查', health.status === 200 && health.data?.status === 'healthy'); const dashboard = await api('/admin/dashboard'); log('管理后台拒绝非管理员', dashboard.status === 403); } // ===== 14. 边界 ===== async function testEdgeCases() { console.log('\n📋 边界情况'); const notFound = await api('/nonexistent'); log('不存在路由404', notFound.status === 404); } // ===== MAIN ===== async function main() { console.log('🚀 TradeMate 全功能 API 测试 v3'); console.log(`📱 手机号: ${PHONE}, 用户名: ${USERNAME}`); console.log(`🕐 ${new Date().toISOString()}`); for (const mod of [testAuth, testCustomers, testProducts, testMarketing, testQuotations, testTranslation, testOnboarding, testFollowup, testAnalytics, testNotifications, testFeedback, testExchange, testAdmin, testEdgeCases]) { if (!token && mod !== testAuth) { console.log(`\n⚠️ 跳过 ${mod.name}`); continue; } await mod(); } console.log(`\n${'='.repeat(40)}`); console.log(`✅ 通过: ${results.pass}`); console.log(`❌ 失败: ${results.fail}`); if (results.errors.length) { console.log(`\n📋 失败详情:`); results.errors.forEach(e => console.log(` ${e}`)); } console.log(`🕐 ${new Date().toISOString()}`); process.exit(results.fail > 0 ? 1 : 0); } main().catch(e => { console.error('💥 测试异常:', e); process.exit(1); });