257 lines
14 KiB
JavaScript
257 lines
14 KiB
JavaScript
// 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); });
|