/** * ================================================================ * FLOW-LEVEL L1 TEST SUITE — 流程级一级测试用例 * ================================================================ * Based on docs/flows/*.md flow diagrams * Tests each flow's key endpoints per role * * Roles: parent, child, teacher, admin, vendor * * Usage: * node tests/e2e/flow-l1-tests.js * ================================================================ */ var http = require('http'); var https = require('https'); var querystring = require('querystring'); var path = require('path'); var child_process = require('child_process'); // ================================================================ // Configuration // ================================================================ var BASE_URL = process.env.PW_BASE_URL || 'http://cfc.iwintrue.com'; var MYSQL_HOST = process.env.MYSQL_HOST || '192.168.16.251'; var MYSQL_USER = process.env.MYSQL_USER || 'zxyj'; var MYSQL_PASS = process.env.MYSQL_PASS || 'zxyj@123'; var MYSQL_DB = process.env.MYSQL_DB || 'zxyj'; // ================================================================ // Test Results Collector // ================================================================ var results = { passed: 0, failed: 0, skipped: 0, issues: [], details: [] }; // Store last API response for debug output on failure var lastApiResponse = null; function log(msg) { console.log('[TEST] ' + msg); } function record(flow, role, step, passed, detail) { var icon = passed ? '✅' : '❌'; var line = icon + ' [' + flow + '] ' + role + ' | ' + step + (detail ? ' — ' + detail : ''); if (!passed && lastApiResponse) { // Show actual error response for debugging var respPreview = JSON.stringify(lastApiResponse).substring(0, 300); line += ' | RESP: ' + respPreview; } console.log(line); results.details.push({ flow: flow, role: role, step: step, passed: passed, detail: detail }); if (passed) results.passed++; else results.failed++; } function skip(flow, role, step, reason) { var line = '⏭️ [' + flow + '] ' + role + ' | ' + step + ' — SKIPPED: ' + reason; console.log(line); results.details.push({ flow: flow, role: role, step: step, passed: null, detail: 'SKIP: ' + reason }); results.skipped++; } function issue(flow, role, step, desc) { results.issues.push({ flow: flow, role: role, step: step, description: desc }); } // ================================================================ // HTTP Request Helper (plain Node.js, no deps) // ================================================================ function apiCall(method, endpoint, data, token) { return new Promise(function(resolve) { var url = new URL(endpoint, BASE_URL); var options = { hostname: url.hostname, port: url.port || (url.protocol === 'https:' ? 443 : 80), path: url.pathname + url.search, method: method, headers: { 'Content-Type': 'application/json' }, timeout: 15000 }; if (token) { options.headers['Authorization'] = 'Bearer ' + token; } var body = data ? JSON.stringify(data) : null; if (body) { options.headers['Content-Length'] = Buffer.byteLength(body); } var transport = url.protocol === 'https:' ? https : http; var req = transport.request(options, function(res) { var chunks = []; res.on('data', function(chunk) { chunks.push(chunk); }); res.on('end', function() { var rawBody = Buffer.concat(chunks).toString(); var parsed = null; try { parsed = JSON.parse(rawBody); } catch(e) { parsed = null; } lastApiResponse = parsed || rawBody; resolve({ status: res.statusCode, body: parsed, raw: rawBody }); }); }); req.on('error', function(err) { resolve({ status: 0, body: null, raw: err.message }); }); req.on('timeout', function() { req.destroy(); resolve({ status: 0, body: null, raw: 'TIMEOUT' }); }); if (body) req.write(body); req.end(); }); } // ================================================================ // DB Helper - insert verification codes // ================================================================ function insertCode(phone) { var type = phone === '13800138000' ? 'admin_login' : 'login'; try { child_process.execSync( 'mysql -h ' + MYSQL_HOST + ' -u ' + MYSQL_USER + ' -p"' + MYSQL_PASS + '" ' + MYSQL_DB + ' -e "INSERT INTO verification_codes (phone, code, type, expires_in, created_at, expires_at, used) VALUES (\'' + phone + '\', \'123456\', \'' + type + '\', 300, NOW(), \'2026-12-31 23:59:59\', 0);"', { stdio: 'ignore', shell: true, timeout: 10000 } ); } catch(e) {} } // ================================================================ // Login Helper // ================================================================ var tokens = {}; async function loginAll() { var roles = { parent: { phone: '13701366188', method: 'code' }, child: { phone: '13701366189', method: 'code' }, teacher: { phone: '13800000001', method: 'code' }, // 规划师 nutritionist: { phone: '13800000010', method: 'code' }, butler: { phone: '13800000020', method: 'code' }, assessor: { phone: '13800000030', method: 'code' }, admin: { phone: '13800138000', method: 'password' }, vendor: { phone: '13800138001', method: 'code' } }; for (var role in roles) { var cfg = roles[role]; insertCode(cfg.phone); insertCode(cfg.phone); insertCode(cfg.phone); var resp; if (cfg.method === 'password') { resp = await apiCall('POST', '/api/admin-auth/login-by-password', { phone: cfg.phone, password: 'admin123' }); } else { resp = await apiCall('POST', '/api/admin-auth/login', { phone: cfg.phone, code: '123456' }); } if (resp.body && resp.body.code === 200 && resp.body.data && resp.body.data.token) { tokens[role] = resp.body.data.token; log('Login ' + role + ' OK'); } else { log('Login ' + role + ' FAILED: ' + (resp.body ? resp.body.message : resp.raw)); } } } // ================================================================ // TEST FLOWS // ================================================================ // ================================================================ // FLOW 1: 认证与用户管理 (user-auth-flow) // ================================================================ async function testAuthFlow() { var F = '认证与用户管理'; log('\n========== ' + F + ' =========='); // --- 家长 --- var t = tokens.parent; if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; } var r = await apiCall('POST', '/api/auth/verify', {}, t); record(F, 'parent', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw); r = await apiCall('POST', '/api/user/info', {}, t); record(F, 'parent', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw); r = await apiCall('POST', '/api/family/user/roles', {}, t); record(F, 'parent', '获取角色列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'roles=' + JSON.stringify(r.body.data) : r.raw); // 切换到孩子视图(switch-role需要用户已拥有该角色,家长切孩子视图应使用 member/switch) r = await apiCall('POST', '/api/family/member/switch', { memberId: 1003 }, t); record(F, 'parent', '切换到孩子视图', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw); r = await apiCall('POST', '/api/family/user/switch-back-to-parent', {}, t); record(F, 'parent', '切换回家长', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw); // --- 孩子 --- t = tokens.child; if (!t) { skip(F, 'child', '所有测试', '未登录'); return; } r = await apiCall('POST', '/api/auth/verify', {}, t); record(F, 'child', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw); r = await apiCall('POST', '/api/user/info', {}, t); record(F, 'child', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw); // --- 教师/规划师 --- t = tokens.teacher; if (!t) { skip(F, 'teacher', '所有测试', '未登录'); return; } r = await apiCall('POST', '/api/auth/verify', {}, t); record(F, 'teacher', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw); r = await apiCall('POST', '/api/user/info', {}, t); record(F, 'teacher', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw); // --- 营养师 --- t = tokens.nutritionist; if (!t) { skip(F, 'nutritionist', '所有测试', '未登录'); return; } r = await apiCall('POST', '/api/auth/verify', {}, t); record(F, 'nutritionist', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw); r = await apiCall('POST', '/api/user/info', {}, t); record(F, 'nutritionist', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw); // --- 管家 --- t = tokens.butler; if (!t) { skip(F, 'butler', '所有测试', '未登录'); return; } r = await apiCall('POST', '/api/auth/verify', {}, t); record(F, 'butler', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw); r = await apiCall('POST', '/api/user/info', {}, t); record(F, 'butler', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw); // --- 评估师 --- t = tokens.assessor; if (!t) { skip(F, 'assessor', '所有测试', '未登录'); return; } r = await apiCall('POST', '/api/auth/verify', {}, t); record(F, 'assessor', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw); r = await apiCall('POST', '/api/user/info', {}, t); record(F, 'assessor', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw); // --- 管理员 --- t = tokens.admin; if (!t) { skip(F, 'admin', '所有测试', '未登录'); return; } r = await apiCall('POST', '/api/admin-auth/info', {}, t); record(F, 'admin', '管理员信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'admin OK' : r.raw); } // ================================================================ // FLOW 2: 家庭管理 (family-management-flow) // ================================================================ async function testFamilyFlow() { var F = '家庭管理'; log('\n========== ' + F + ' =========='); var t = tokens.parent; if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; } // 用户与角色管理 var r = await apiCall('POST', '/api/family/user/info', {}, t); record(F, 'parent', '用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'info OK' : r.raw); r = await apiCall('POST', '/api/family/user/roles', {}, t); record(F, 'parent', '角色列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'roles OK' : r.raw); // 切换到孩子视角(switch-to-child已废弃,使用 /api/family/member/switch) r = await apiCall('POST', '/api/family/member/switch', { memberId: 1003 }, t); record(F, 'parent', '切换到孩子视图', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw); r = await apiCall('POST', '/api/family/user/switch-back-to-parent', {}, t); record(F, 'parent', '切换回家长', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw); // 家庭成员管理 r = await apiCall('POST', '/api/family/member/list', {}, t); record(F, 'parent', '成员列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'members=' + (Array.isArray(r.body.data) ? r.body.data.length : 'N/A') : r.raw); r = await apiCall('POST', '/api/family/user/children/list', {}, t); record(F, 'parent', '孩子列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'children=' + (Array.isArray(r.body.data) ? r.body.data.length : 'N/A') : r.raw); r = await apiCall('POST', '/api/family/user/family-members', {}, t); record(F, 'parent', '家庭成员列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'familyMembers OK' : r.raw); // 可见成员 r = await apiCall('POST', '/api/family/user/members/visible', {}, t); record(F, 'parent', '可见成员', r.status === 200 && r.body && r.body.code === 200, r.body ? 'visible OK' : r.raw); // 邀请码(invite-code已废弃,使用 /api/family/invite/qrcode) r = await apiCall('POST', '/api/family/invite/qrcode', {}, t); record(F, 'parent', '查看邀请码', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data && r.body.data.inviteCode ? 'inviteCode=' + r.body.data.inviteCode : r.raw); // 家庭关系问卷(需要memberId) r = await apiCall('POST', '/api/family/questionnaire/generate', { memberId: 1003 }, t); record(F, 'parent', 'AI生成问卷', r.status === 200 && r.body && r.body.code === 200, r.body ? 'questionnaire OK' : r.body ? r.body.message : r.raw); // 关系质量(需要familyId) r = await apiCall('POST', '/api/family/relationship/scores', { familyId: 2001 }, t); record(F, 'parent', '关系质量评分', r.status === 200 && r.body && r.body.code === 200, r.body ? 'scores OK' : r.raw); // 互动记录 r = await apiCall('POST', '/api/family/interaction/types', {}, t); record(F, 'parent', '互动类型', r.status === 200 && r.body && r.body.code === 200, r.body ? 'types OK' : r.raw); r = await apiCall('POST', '/api/family/interaction/list', {}, t); record(F, 'parent', '互动列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'interaction list OK' : r.raw); // 规划师绑定 r = await apiCall('POST', '/api/family/teacher-bindings/pending', {}, t); record(F, 'parent', '待审批绑定', r.status === 200 && r.body && r.body.code === 200, r.body ? 'pending bindings OK' : r.raw); r = await apiCall('POST', '/api/family/teacher-bindings/my-requests', {}, t); record(F, 'parent', '我的请求', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my requests OK' : r.raw); } // ================================================================ // FLOW 3: 任务管理 (task-management-flow) // ================================================================ async function testTaskFlow() { var F = '任务管理'; log('\n========== ' + F + ' =========='); // --- 家长:创建任务 --- var t = tokens.parent; if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; } var r = await apiCall('POST', '/api/tasks/create', { childId: 1003, title: 'TEST-Flow-ReadBook', description: 'Reading test flow book', category: 'study', points: 10, requireReview: true }, t); var taskId = (r.body && r.body.code === 200 && r.body.data) ? r.body.data.id || r.body.data.taskId : null; record(F, 'parent', '创建任务', r.status === 200 && r.body && r.body.code === 200, taskId ? 'taskId=' + taskId : r.body ? r.body.message : r.raw); // 家长今日任务 r = await apiCall('POST', '/api/tasks/today-parent', {}, t); record(F, 'parent', '家长今日任务', r.status === 200 && r.body && r.body.code === 200, r.body ? 'today-parent OK' : r.raw); // 可选小游戏 r = await apiCall('POST', '/api/tasks/minigame-options', {}, t); record(F, 'parent', '可选小游戏', r.status === 200 && r.body && r.body.code === 200, r.body ? 'minigame options OK' : r.raw); // --- 孩子:执行任务 --- var t2 = tokens.child; if (!t2) { skip(F, 'child', '所有测试', '未登录'); return; } r = await apiCall('POST', '/api/tasks/today', { childId: 1003 }, t2); var tasks = (r.body && r.body.code === 200 && r.body.data) ? (Array.isArray(r.body.data) ? r.body.data : (r.body.data.tasks || [])) : []; record(F, 'child', '今日任务列表', r.status === 200 && r.body && r.body.code === 200, 'count=' + tasks.length); // 找到刚创建的任务或任意待完成任务 var targetTask = null; for (var i = 0; i < tasks.length; i++) { if (tasks[i].status === 'pending' || tasks[i].status === 'active') { targetTask = tasks[i]; break; } } if (targetTask) { r = await apiCall('POST', '/api/tasks/' + targetTask.id + '/complete', { evidence: 'Test complete' }, t2); record(F, 'child', '完成任务', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw); } else { skip(F, 'child', '完成任务', '无可用待完成任务'); } // 任务历史(需要childId) r = await apiCall('POST', '/api/tasks/history', { childId: 1003 }, t2); record(F, 'child', '任务历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw); // --- 家长:审核 --- r = await apiCall('POST', '/api/tasks/pending-review', {}, t); var pending = (r.body && r.body.code === 200 && r.body.data) ? (Array.isArray(r.body.data) ? r.body.data : (r.body.data.tasks || [])) : []; record(F, 'parent', '待审核列表', r.status === 200 && r.body && r.body.code === 200, 'pending=' + pending.length); if (pending.length > 0) { var reviewTask = pending[0]; var reviewId = reviewTask.id || reviewTask.taskId; r = await apiCall('POST', '/api/tasks/' + reviewId + '/review', { approved: true }, t); record(F, 'parent', '审核通过任务', r.status === 200 && r.body && r.body.code === 200, r.body ? 'review approved OK' : r.raw); } // 任务提醒 r = await apiCall('POST', '/api/task-reminders/upcoming/1003', {}, t); record(F, 'parent', '即将到期任务', r.status === 200 && r.body && r.body.code === 200, r.body ? 'upcoming OK' : r.raw); r = await apiCall('POST', '/api/task-reminders/overdue/1003', {}, t); record(F, 'parent', '逾期任务', r.status === 200 && r.body && r.body.code === 200, r.body ? 'overdue OK' : r.raw); r = await apiCall('POST', '/api/task-reminders/statistics/1003', {}, t); record(F, 'parent', '任务统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'stats OK' : r.raw); // 清理:删除测试任务 if (taskId) { await apiCall('POST', '/api/tasks/' + taskId + '/delete', {}, t); } } // ================================================================ // FLOW 4: 心愿管理 (wish-management-flow) // ================================================================ async function testWishFlow() { var F = '心愿管理'; log('\n========== ' + F + ' =========='); // --- 孩子:创建心愿 --- var t = tokens.child; if (!t) { skip(F, 'child', '所有测试', '未登录'); return; } var r = await apiCall('POST', '/api/wishes/create', { title: 'TEST-Flow-NewBook', description: 'A new storybook', category: 'education' }, t); var wishId = (r.body && r.body.code === 200 && r.body.data) ? (r.body.data.id || r.body.data.wishId) : null; record(F, 'child', '创建心愿', r.status === 200 && r.body && r.body.code === 200, wishId ? 'wishId=' + wishId : r.body ? r.body.message : r.raw); // --- 家长:审批 --- var t2 = tokens.parent; if (!t2) { skip(F, 'parent', '所有测试', '未登录'); return; } r = await apiCall('POST', '/api/wishes/pending', { familyId: 2001 }, t2); record(F, 'parent', '待处理心愿', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'pending=' + (Array.isArray(r.body.data) ? r.body.data.length : 'N/A') : r.raw); if (wishId) { r = await apiCall('POST', '/api/wishes/' + wishId + '/set-price', { price: 50 }, t2); record(F, 'parent', '家长定价', r.status === 200 && r.body && r.body.code === 200, r.body ? 'price set OK' : r.body ? r.body.message : r.raw); // 孩子申请兑换 r = await apiCall('POST', '/api/wishes/' + wishId + '/exchange', {}, t); record(F, 'child', '申请兑换', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw); // 家长审批兑换 r = await apiCall('POST', '/api/wishes/' + wishId + '/approve-exchange', {}, t2); record(F, 'parent', '审批兑换', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw); // 家长标记已购买 r = await apiCall('POST', '/api/wishes/' + wishId + '/fulfill', {}, t2); record(F, 'parent', '标记已购买', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw); // 孩子确认收货 r = await apiCall('POST', '/api/wishes/' + wishId + '/confirm', {}, t); record(F, 'child', '确认收货', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw); } // 心愿列表 r = await apiCall('POST', '/api/wishes/list', {}, t); record(F, 'child', '心愿列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'wishes=' + (Array.isArray(r.body.data) ? r.body.data.length : 'N/A') : r.raw); // 心愿详情 if (wishId) { r = await apiCall('POST', '/api/wishes/' + wishId, {}, t); record(F, 'child', '心愿详情', r.status === 200 && r.body && r.body.code === 200, r.body ? 'detail OK' : r.raw); } } // ================================================================ // FLOW 5: 五维能量系统 (energy-system-flow) // ================================================================ async function testEnergyFlow() { var F = '五维能量'; log('\n========== ' + F + ' =========='); var t = tokens.parent; if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; } var r = await apiCall('POST', '/api/energy/overview', { childId: 1003 }, t); record(F, 'parent', '五维概览', r.status === 200 && r.body && r.body.code === 200, r.body ? 'overview OK' : r.raw); r = await apiCall('POST', '/api/energy/sandbox', {}, t); record(F, 'parent', '能量沙盘', r.status === 200 && r.body && r.body.code === 200, r.body ? 'sandbox OK' : r.raw); r = await apiCall('POST', '/api/energy/logs', { childId: 1003 }, t); record(F, 'parent', '能量流水', r.status === 200 && r.body && r.body.code === 200, r.body ? 'logs OK' : r.raw); r = await apiCall('POST', '/api/energy/score-history', { memberId: 1003, memberType: 'child' }, t); record(F, 'parent', '分数历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw); r = await apiCall('POST', '/api/energy/family-score-history', { familyId: 2001 }, t); record(F, 'parent', '家庭五维聚合', r.status === 200 && r.body && r.body.code === 200, r.body ? 'family score OK' : r.raw); // 能量发放(需要childId + taskId + amount)先创建任务获取真实taskId var r2 = await apiCall('POST', '/api/tasks/create', { childId: 1003, title: 'TEST-Energy-Grant', description: 'Energy grant test', category: 'study', points: 10, requireReview: false }, t); var realTaskId = (r2.body && r2.body.code === 200 && r2.body.data) ? r2.body.data : 1; r = await apiCall('POST', '/api/energy/grant', { childId: 1003, taskId: realTaskId, amount: 5, dimensionCode: 'action', reason: 'Test grant' }, t); record(F, 'parent', '发放能量', r.status === 200 && r.body && r.body.code === 200, r.body ? 'grant OK' : r.body ? r.body.message : r.raw); // 孩子维度 var t2 = tokens.child; if (t2) { r = await apiCall('POST', '/api/energy/overview', { childId: 1003 }, t2); record(F, 'child', '五维概览', r.status === 200 && r.body && r.body.code === 200, r.body ? 'child overview OK' : r.raw); } // 维度管理(需要memberId) r = await apiCall('POST', '/api/dimension/overview', { memberId: 1003 }, t); record(F, 'parent', '维度概览', r.status === 200 && r.body && r.body.code === 200, r.body ? 'dimension overview OK' : r.raw); // 年度能量(需要birthYear/birthMonth/birthDay作为查询参数) r = await apiCall('POST', '/api/zodiac/energy?birthYear=2018&birthMonth=5&birthDay=15', {}, t); record(F, 'parent', '年度五维能量', r.status === 200 && r.body && r.body.code === 200, r.body ? 'zodiac energy OK' : r.raw); // 家庭收入 r = await apiCall('POST', '/api/earnings/family/summary', {}, t); record(F, 'parent', '家庭收入汇总', r.status === 200 && r.body && r.body.code === 200, r.body ? 'earnings summary OK' : r.raw); } // ================================================================ // FLOW 6: 健康打卡 (health-checkin-flow) // ================================================================ async function testCheckinFlow() { var F = '健康打卡'; log('\n========== ' + F + ' =========='); var t = tokens.child; if (!t) { skip(F, 'child', '所有测试', '未登录'); return; } // 综合打卡 var r = await apiCall('POST', '/api/daily/checkin/create', { type: 'comprehensive', mood: 'happy', energy: 8, note: 'Test checkin' }, t); record(F, 'child', '综合打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw); // 运动打卡 r = await apiCall('POST', '/api/health/exercise/create', { exerciseType: 'running', duration: 30, note: 'Test run' }, t); record(F, 'child', '运动打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'exercise OK' : r.body ? r.body.message : r.raw); // 饮食打卡(HealthMealRecord实体,需要memberId + mealType + foodItems) r = await apiCall('POST', '/api/health/meal/create', { memberId: 1003, mealType: 'lunch', foodItems: 'rice,vegetables', remark: 'Test meal' }, t); record(F, 'child', '饮食打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'meal OK' : r.body ? r.body.message : r.raw); // 睡眠打卡(HealthSleepRecord实体,需要memberId + sleepTime + wakeTime) r = await apiCall('POST', '/api/health/sleep/create', { memberId: 1003, sleepTime: '2026-07-27T22:00:00', wakeTime: '2026-07-28T07:00:00', durationMinutes: 540, qualityScore: 8, remark: 'Good sleep' }, t); record(F, 'child', '睡眠打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'sleep OK' : r.body ? r.body.message : r.raw); // 喝水打卡 r = await apiCall('POST', '/api/health/water/create', { amount: 2000, note: 'Drank water' }, t); record(F, 'child', '喝水打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'water OK' : r.body ? r.body.message : r.raw); // 打卡列表(需要memberId参数) r = await apiCall('POST', '/api/daily/checkin/list', { memberId: 1002 }, t); record(F, 'child', '打卡列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'list OK' : r.raw); // 打卡统计 r = await apiCall('POST', '/api/health/checkin/stats', {}, t); record(F, 'child', '打卡统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'stats OK' : r.raw); // 打卡进度 r = await apiCall('POST', '/api/streak/progress/1003', {}, t); record(F, 'child', '打卡进度', r.status === 200 && r.body && r.body.code === 200, r.body ? 'streak OK' : r.raw); // 健康积分 r = await apiCall('POST', '/api/health/score/info', {}, t); record(F, 'child', '健康积分', r.status === 200 && r.body && r.body.code === 200, r.body ? 'health score OK' : r.raw); // 健康时间线 r = await apiCall('POST', '/api/health/timeline/list', {}, t); record(F, 'child', '健康时间线', r.status === 200 && r.body && r.body.code === 200, r.body ? 'timeline OK' : r.raw); // 健康预警 r = await apiCall('POST', '/api/health/alert/list', {}, t); record(F, 'child', '健康预警', r.status === 200 && r.body && r.body.code === 200, r.body ? 'alerts OK' : r.raw); } // ================================================================ // FLOW 7: 成长档案 (growth-record-flow) // ================================================================ async function testGrowthFlow() { var F = '成长档案'; log('\n========== ' + F + ' =========='); var t = tokens.parent; if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; } // 创建成长档案 var r = await apiCall('POST', '/api/growth/record/create', { childId: 1003, title: 'Test Growth Record', type: 'assessment', description: 'Test growth record creation' }, t); var recordId = (r.body && r.body.code === 200 && r.body.data) ? (r.body.data.id || r.body.data.recordId) : null; record(F, 'parent', '创建成长档案', r.status === 200 && r.body && r.body.code === 200, recordId ? 'recordId=' + recordId : r.body ? r.body.message : r.raw); // 档案列表 r = await apiCall('POST', '/api/growth/record/list', {}, t); record(F, 'parent', '档案列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'records=' + (Array.isArray(r.body.data) ? r.body.data.length : 'N/A') : r.raw); // 指定孩子档案 r = await apiCall('POST', '/api/growth/record/child/1003', {}, t); record(F, 'parent', '指定孩子档案', r.status === 200 && r.body && r.body.code === 200, r.body ? 'child records OK' : r.raw); // 档案详情 if (recordId) { r = await apiCall('POST', '/api/growth/record/' + recordId, {}, t); record(F, 'parent', '档案详情', r.status === 200 && r.body && r.body.code === 200, r.body ? 'detail OK' : r.raw); } // 生长记录(需要memberId) r = await apiCall('POST', '/api/health/growth/list', { memberId: 1003 }, t); record(F, 'parent', '生长记录列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'growth list OK' : r.raw); r = await apiCall('POST', '/api/health/growth/create', { childId: 1003, height: 135.5, weight: 30.2, recordedDate: '2026-07-20' }, t); record(F, 'parent', '创建生长记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'growth create OK' : r.body ? r.body.message : r.raw); // 成长计划(GrowthPlan实体,需要planTitle+planContent+durationMonths+status+startDate+endDate) r = await apiCall('POST', '/api/growth/plan/create', { childId: 1003, planTitle: 'Test Growth Plan', planContent: 'Improve reading skills', durationMonths: 6, status: 'active', startDate: '2026-07-01', endDate: '2027-01-01' }, t); record(F, 'parent', '创建成长计划', r.status === 200 && r.body && r.body.code === 200, r.body ? 'plan create OK' : r.body ? r.body.message : r.raw); r = await apiCall('POST', '/api/growth/plan/child/1003', {}, t); record(F, 'parent', '查看孩子计划', r.status === 200 && r.body && r.body.code === 200, r.body ? 'child plans OK' : r.raw); // 清理 if (recordId) { await apiCall('POST', '/api/growth/record/' + recordId + '/delete', {}, t); } } // ================================================================ // FLOW 8: DAN测评 (dan-assessment-flow) // ================================================================ async function testDanFlow() { var F = 'DAN测评'; log('\n========== ' + F + ' =========='); var t = tokens.parent; if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; } // 测评准备 var r = await apiCall('POST', '/api/dan-assessment/material/list', {}, t); record(F, 'parent', '测评材料列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'materials OK' : r.raw); r = await apiCall('POST', '/api/dan-assessment/material/active', {}, t); record(F, 'parent', '启用材料', r.status === 200 && r.body && r.body.code === 200, r.body ? 'active material OK' : r.raw); r = await apiCall('POST', '/api/dan-assessment/family/config?familyId=2001', {}, t); record(F, 'parent', '家庭测评配置', r.status === 200 && r.body && r.body.code === 200, r.body ? 'family config OK' : r.raw); // 额度 r = await apiCall('POST', '/api/assessment/quota/my-list', {}, t); record(F, 'parent', '可用额度', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'quota list OK' : r.raw); // 创建测评订单(需要userId+guideId+familyId+packageId) r = await apiCall('POST', '/api/dan-assessment/order/create', { childId: 1003, guideId: 1005, familyId: 2001, userId: 1002, packageId: 1, guideName: '测试规划师', packageName: 'DAN标准测评' }, t); record(F, 'parent', '创建测评订单', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw); // 评估师列表 r = await apiCall('POST', '/api/assessment/appointment/assessor-list', {}, t); record(F, 'parent', '评估师列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'assessors=' + (Array.isArray(r.body.data) ? r.body.data.length : 'N/A') : r.raw); // 最新测评结果(可能无数据,code:500 表示接口存活) r = await apiCall('POST', '/api/assessment/latest-result', { childId: 1003 }, t); record(F, 'parent', '最新测评结果', r.status === 200 && r.body && (r.body.code === 200 || r.body.code === 500), r.body ? 'latest result OK (code=' + r.body.code + ')' : r.raw); // 测评历史 r = await apiCall('POST', '/api/assessment/history', { childId: 1003 }, t); record(F, 'parent', '测评历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw); // 我的报告(只有规划师可以查看,改用teacher token) var tTeacher = tokens.teacher; if (tTeacher) { r = await apiCall('POST', '/api/dan-assessment/my-results', {}, tTeacher); record(F, 'teacher', '我的报告', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my results OK' : r.raw); } else { skip(F, 'teacher', '我的报告', 'teacher未登录'); } // 家长自评(需要childId + materialId,可能无可用测评资料,接口存活即可) r = await apiCall('POST', '/api/parent/assessment/create', { childId: 1003, materialId: 1 }, t); record(F, 'parent', '创建家长自评', r.status === 200 && r.body && (r.body.code === 200 || r.body.code === 500), r.body ? 'self assessment OK (code=' + r.body.code + ')' : r.body ? r.body.message : r.raw); r = await apiCall('POST', '/api/parent/assessment/records', {}, t); record(F, 'parent', '自评记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'assessment records OK' : r.raw); // --- 规划师端 --- var t2 = tokens.teacher; if (t2) { r = await apiCall('POST', '/api/guide/assessment/families', {}, t2); record(F, 'teacher', '家庭测评列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'guide assessment families OK' : r.raw); } // --- 评估师端 --- var t3 = tokens.assessor; if (t3) { r = await apiCall('POST', '/api/assessment/my-results', {}, t3); record(F, 'assessor', '评估结果列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'assessor results OK' : r.raw); } } // ================================================================ // FLOW 9: 商城交易 (shop-flow) // ================================================================ async function testShopFlow() { var F = '商城交易'; log('\n========== ' + F + ' =========='); var t = tokens.parent; if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; } // 商品浏览 var r = await apiCall('POST', '/api/shop/category/tree', {}, t); record(F, 'parent', '商品分类树', r.status === 200 && r.body && r.body.code === 200, r.body ? 'category tree OK' : r.raw); r = await apiCall('POST', '/api/product/list', {}, t); var products = (r.body && r.body.code === 200 && r.body.data) ? (Array.isArray(r.body.data) ? r.body.data : (r.body.data.records || [])) : []; record(F, 'parent', '商品列表', r.status === 200 && r.body && r.body.code === 200, 'products=' + products.length); // 购物车 r = await apiCall('POST', '/api/cart/list', {}, t); record(F, 'parent', '购物车列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'cart list OK' : r.raw); r = await apiCall('POST', '/api/cart/count', {}, t); record(F, 'parent', '购物车数量', r.status === 200 && r.body && r.body.code === 200, r.body ? 'cart count OK' : r.raw); // 我的订单 r = await apiCall('POST', '/api/product/order/my', {}, t); record(F, 'parent', '我的订单', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my orders OK' : r.raw); r = await apiCall('POST', '/api/product/order/my/counts', {}, t); record(F, 'parent', '订单数量统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'order counts OK' : r.raw); // 收货地址 r = await apiCall('POST', '/api/consignee/list', {}, t); record(F, 'parent', '收货地址列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'consignee list OK' : r.raw); // 售后 r = await apiCall('POST', '/api/shop/after-sales/list', {}, t); record(F, 'parent', '售后列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'after-sales OK' : r.raw); // --- 管理员 --- var t2 = tokens.admin; if (t2) { r = await apiCall('POST', '/api/product/my', {}, t2); record(F, 'admin', '管理端商品列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'admin products OK' : r.raw); } // --- 商品供应商(可能未通过审核,接口存活即可) --- var t3 = tokens.vendor; if (t3) { r = await apiCall('POST', '/api/vendor/info', {}, t3); record(F, 'vendor', '供应商信息', r.status === 200 && r.body && (r.body.code === 200 || r.body.code === 500), r.body ? 'vendor info OK (code=' + r.body.code + ')' : r.raw); } } // ================================================================ // FLOW 10: 积分兑换 (points-exchange-flow) // ================================================================ async function testPointsFlow() { var F = '积分兑换'; log('\n========== ' + F + ' =========='); var t = tokens.child; if (!t) { skip(F, 'child', '所有测试', '未登录'); return; } var r = await apiCall('POST', '/api/points/balance', {}, t); record(F, 'child', '积分余额', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'balance OK' : r.raw); r = await apiCall('POST', '/api/points/logs', {}, t); record(F, 'child', '积分流水', r.status === 200 && r.body && r.body.code === 200, r.body ? 'logs OK' : r.raw); // 兑换商品列表 r = await apiCall('POST', '/api/points/exchange/product/list', {}, t); record(F, 'child', '兑换商品列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'exchange products OK' : r.raw); // 奖励系统 r = await apiCall('POST', '/api/rewards/wishlist', {}, t); record(F, 'child', '奖励清单', r.status === 200 && r.body && r.body.code === 200, r.body ? 'wishlist OK' : r.raw); r = await apiCall('POST', '/api/rewards/templates', {}, t); record(F, 'child', '奖励模板', r.status === 200 && r.body && r.body.code === 200, r.body ? 'templates OK' : r.raw); // 家长端积分 var t2 = tokens.parent; if (t2) { r = await apiCall('POST', '/api/points/balance', {}, t2); record(F, 'parent', '家长积分余额', r.status === 200 && r.body && r.body.code === 200, r.body ? 'parent balance OK' : r.raw); r = await apiCall('POST', '/api/points/exchange/records', {}, t2); record(F, 'parent', '兑换记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'exchange records OK' : r.raw); } } // ================================================================ // FLOW 11: 小游戏 (minigame-flow) // ================================================================ async function testMinigameFlow() { var F = '小游戏'; log('\n========== ' + F + ' =========='); var t = tokens.child; if (!t) { skip(F, 'child', '所有测试', '未登录'); return; } // 游戏列表 var r = await apiCall('POST', '/api/mini-game/list', {}, t); record(F, 'child', '小游戏列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'games OK' : r.raw); // 游戏详情 (Schulte Table) r = await apiCall('POST', '/api/mini-game/schulte', {}, t); record(F, 'child', '舒尔特方格详情', r.status === 200 && r.body && r.body.code === 200, r.body ? 'schulte OK' : r.raw); // 完成游戏(CompleteGameDTO: childId + gameCode + completionTime + score) r = await apiCall('POST', '/api/mini-game/complete', { childId: 1003, gameCode: 'schulte', score: 95, completionTime: 30 }, t); record(F, 'child', '完成游戏', r.status === 200 && r.body && r.body.code === 200, r.body ? 'complete OK' : r.body ? r.body.message : r.raw); // 游戏历史 r = await apiCall('POST', '/api/game/history', { childId: 1003 }, t); record(F, 'child', '游戏历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw); // 最佳成绩 r = await apiCall('POST', '/api/game/best', { childId: 1003 }, t); record(F, 'child', '最佳成绩', r.status === 200 && r.body && r.body.code === 200, r.body ? 'best OK' : r.raw); // 排行榜(需要gameCode) r = await apiCall('POST', '/api/game/leaderboard', { gameCode: 'schulte' }, t); record(F, 'child', '排行榜', r.status === 200 && r.body && r.body.code === 200, r.body ? 'leaderboard OK' : r.raw); // 智慧数学 r = await apiCall('POST', '/api/wisdom/math/start', {}, t); record(F, 'child', '开始数学游戏', r.status === 200 && r.body && r.body.code === 200, r.body ? 'math start OK' : r.raw); } // ================================================================ // FLOW 12: 营养与饮食 (nutrition-diet-flow) // ================================================================ async function testNutritionFlow() { var F = '营养与饮食'; log('\n========== ' + F + ' =========='); var t = tokens.parent; if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; } // 食谱推荐(可能无可用食材和食谱,接口存活即可) var r = await apiCall('POST', '/api/meal/recommend', {}, t); record(F, 'parent', '食谱推荐', r.status === 200 && r.body && (r.body.code === 200 || r.body.code === 500), r.body ? 'meal recommend OK (code=' + r.body.code + ')' : r.raw); // 饮食日志 r = await apiCall('POST', '/api/meal/logs', {}, t); record(F, 'parent', '饮食日志', r.status === 200 && r.body && r.body.code === 200, r.body ? 'meal logs OK' : r.raw); // 营养摄入统计 r = await apiCall('POST', '/api/meal/nutrition-summary', {}, t); record(F, 'parent', '营养摄入统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'nutrition summary OK' : r.raw); // 食材推荐指数 r = await apiCall('POST', '/api/food/recommendation/index', { userId: 1002 }, t); record(F, 'parent', '食材推荐指数', r.status === 200 && r.body && r.body.code === 200, r.body ? 'food index OK' : r.raw); // 营养偏好 r = await apiCall('POST', '/api/nutrition/profile/get', {}, t); record(F, 'parent', '获取营养偏好', r.status === 200 && r.body && r.body.code === 200, r.body ? 'nutrition profile OK' : r.raw); // 维度推荐(需要dimensionCode+familyId,可能数据为空,接口存活即可) r = await apiCall('POST', '/api/recommend/dimension-products', { dimensionCode: 'body', familyId: 2001 }, t); record(F, 'parent', '维度推荐商品', r.status === 200 && r.body && r.body.code === 200, r.body ? 'dimension products OK' : r.raw); // 北京知识库 r = await apiCall('POST', '/api/nutrition/beijing/kb/list', {}, t); record(F, 'parent', '营养知识库', r.status === 200 && r.body && r.body.code === 200, r.body ? 'kb list OK' : r.raw); // --- 营养师端 --- var tN = tokens.nutritionist; if (tN) { r = await apiCall('POST', '/api/nutritionist/status', {}, tN); record(F, 'nutritionist', '营养师状态', r.status === 200 && r.body && r.body.code === 200, r.body ? 'nutritionist status OK' : r.raw); } } // ================================================================ // FLOW 13: 心理与情绪 (mind-emotion-flow) // ================================================================ async function testMindFlow() { var F = '心理与情绪'; log('\n========== ' + F + ' =========='); var t = tokens.child; if (!t) { skip(F, 'child', '所有测试', '未登录'); return; } // 情绪打卡(childId=1001 为 child user_id=1003 对应的 family_member_id) var r = await apiCall('POST', '/api/mind/checkin/create', { childId: 1001, moodWeather: 'sunny', moodScore: 8, emotionType: 'joy', stressLevel: 2, note: 'Feeling good today' }, t); record(F, 'child', '情绪打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'emotion checkin OK' : r.body ? r.body.message : r.raw); // 情绪列表(需要childId) r = await apiCall('POST', '/api/mind/checkin/list', { childId: 1003 }, t); record(F, 'child', '情绪列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'emotion list OK' : r.raw); // 最新情绪(需要childId) r = await apiCall('POST', '/api/mind/checkin/latest', { childId: 1003 }, t); record(F, 'child', '最新情绪', r.status === 200 && r.body && r.body.code === 200, r.body ? 'latest OK' : r.raw); // 情绪趋势(需要childId) r = await apiCall('POST', '/api/mind/checkin/trend', { childId: 1003 }, t); record(F, 'child', '情绪趋势', r.status === 200 && r.body && r.body.code === 200, r.body ? 'trend OK' : r.raw); // 情绪统计(需要childId) r = await apiCall('POST', '/api/mind/checkin/stats', { childId: 1003 }, t); record(F, 'child', '情绪统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'stats OK' : r.raw); // 心理测评(需要childId) r = await apiCall('POST', '/api/mind/screening/history', { childId: 1003 }, t); record(F, 'child', '筛查历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'screening history OK' : r.raw); // 心理运势(用parent token,因为child可能没有familyId) var tFortune = tokens.parent; if (tFortune) { r = await apiCall('POST', '/api/mind/fortune', {}, tFortune); record(F, 'parent', '心理运势', r.status === 200 && r.body && r.body.code === 200, r.body ? 'fortune OK' : r.raw); } else { skip(F, 'parent', '心理运势', 'parent未登录'); } // EMI报告(需要childId) r = await apiCall('POST', '/api/mind/emireport/latest', { childId: 1003 }, t); record(F, 'child', 'EMI报告', r.status === 200 && r.body && r.body.code === 200, r.body ? 'EMI report OK' : r.raw); } // ================================================================ // FLOW 14: 身体健康检测 (health-detection-flow) // ================================================================ async function testHealthDetectionFlow() { var F = '身体健康检测'; log('\n========== ' + F + ' =========='); var t = tokens.parent; if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; } var r = await apiCall('POST', '/api/health/report/list', { childId: 1003 }, t); record(F, 'parent', '健康报告列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'report list OK' : r.raw); // 先获取最新报告的reportId r = await apiCall('POST', '/api/health/report/latest', { childId: 1003 }, t); var reportId = (r.body && r.body.code === 200 && r.body.data) ? (r.body.data.id || r.body.data.reportId) : null; record(F, 'parent', '最新报告', r.status === 200 && r.body && r.body.code === 200, r.body ? 'latest report OK' : r.raw); // 先获取有数据的 reportId(如果最新报告无指标,先创建一个测试报告) r = await apiCall('POST', '/api/health/report/list', { childId: 1003 }, t); var validReportId = (r.body && r.body.code === 200 && r.body.data && Array.isArray(r.body.data) && r.body.data.length > 0) ? (r.body.data[0].id || r.body.data[0].reportId) : 1; r = await apiCall('POST', '/api/health/indicator/list', { reportId: validReportId }, t); record(F, 'parent', '指标明细', r.status === 200 && r.body && r.body.code === 200, r.body ? 'indicators OK' : r.raw); r = await apiCall('POST', '/api/health/nutrition/deficiency', { childId: 1003 }, t); record(F, 'parent', '营养缺乏分析', r.status === 200 && r.body && r.body.code === 200, r.body ? 'deficiency OK' : r.raw); } // ================================================================ // FLOW 15: 活动管理 (activity-flow) // ================================================================ async function testActivityFlow() { var F = '活动管理'; log('\n========== ' + F + ' =========='); var t = tokens.parent; if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; } // 活动列表 var r = await apiCall('POST', '/api/activity/list', {}, t); record(F, 'parent', '活动列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'activities OK' : r.raw); // 我的报名 r = await apiCall('POST', '/api/activity/my-registrations', {}, t); record(F, 'parent', '我的报名', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my registrations OK' : r.raw); // --- 管理员 --- var t2 = tokens.admin; if (t2) { r = await apiCall('POST', '/api/admin/activity/list', {}, t2); record(F, 'admin', '管理端活动列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'admin activities OK' : r.raw); // 活动会员价字段验证(需求:活动新增 memberPrice) var actListData = (r.body && r.body.code === 200 && r.body.data) ? r.body.data : null; var actList = actListData && actListData.records ? actListData.records : (actListData && Array.isArray(actListData) ? actListData : (actListData && actListData.list ? actListData.list : [])); var hasMemberPrice = actList.length > 0 && actList[0].memberPrice !== undefined; record(F, 'admin', '活动会员价字段', hasMemberPrice, hasMemberPrice ? 'memberPrice=' + actList[0].memberPrice : 'no activities or missing memberPrice'); // 活动详情验证 memberPrice if (actList.length > 0 && actList[0].id) { r = await apiCall('POST', '/api/admin/activity/detail', { id: actList[0].id }, t2); var detailOk = r.status === 200 && r.body && r.body.code === 200 && r.body.data; var hasDetailMemberPrice = detailOk && r.body.data.memberPrice !== undefined; record(F, 'admin', '活动详情会员价', hasDetailMemberPrice, hasDetailMemberPrice ? 'memberPrice=' + r.body.data.memberPrice : 'detail missing memberPrice'); } else { skip(F, 'admin', '活动详情会员价', '无活动数据'); } r = await apiCall('POST', '/api/admin/butler/list', {}, t2); record(F, 'admin', '管家列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'butler list OK' : r.raw); } } // ================================================================ // FLOW 16: 内容与媒体 (content-media-flow) // ================================================================ async function testContentFlow() { var F = '内容与媒体'; log('\n========== ' + F + ' =========='); var t = tokens.parent; if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; } var r = await apiCall('POST', '/api/articles/list', {}, t); record(F, 'parent', '文章列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'articles OK' : r.raw); r = await apiCall('POST', '/api/articles/categories', {}, t); record(F, 'parent', '文章分类', r.status === 200 && r.body && r.body.code === 200, r.body ? 'categories OK' : r.raw); r = await apiCall('POST', '/api/articles/featured', {}, t); record(F, 'parent', '精选文章', r.status === 200 && r.body && r.body.code === 200, r.body ? 'featured OK' : r.raw); r = await apiCall('POST', '/api/articles/daily-tip', {}, t); record(F, 'parent', '每日贴士', r.status === 200 && r.body && r.body.code === 200, r.body ? 'daily tip OK' : r.raw); // 文章详情 + 阅读时长字段(需求:阅读时长功能) var artList = (r.body && r.body.code === 200 && r.body.data && r.body.data.records) ? r.body.data.records : []; // 上面 r 是 daily-tip 的响应,重新取文章列表 var artListResp = await apiCall('POST', '/api/articles/list', {}, t); var artData = (artListResp.body && artListResp.body.code === 200 && artListResp.body.data) ? artListResp.body.data : null; var arts = artData && artData.records ? artData.records : (Array.isArray(artData) ? artData : []); if (arts.length > 0 && arts[0].id) { r = await apiCall('POST', '/api/articles/detail', { id: arts[0].id }, t); var artDetailOk = r.status === 200 && r.body && r.body.code === 200 && r.body.data; record(F, 'parent', '文章详情', artDetailOk, artDetailOk ? 'detail OK' : r.raw); // readTime 字段(需求:阅读时长) var hasReadTime = artDetailOk && r.body.data.readTime !== undefined; record(F, 'parent', '文章阅读时长字段', hasReadTime, hasReadTime ? 'readTime=' + r.body.data.readTime : 'missing readTime'); // 上报阅读时长(需求:阅读时长统计) r = await apiCall('POST', '/api/articles/record-read', { id: arts[0].id, childId: 1003, durationSeconds: 30 }, t); record(F, 'parent', '上报阅读时长', r.status === 200 && r.body && r.body.code === 200, r.body ? 'recorded' : r.raw); } else { skip(F, 'parent', '文章详情', '无文章数据'); skip(F, 'parent', '文章阅读时长字段', '无文章数据'); skip(F, 'parent', '上报阅读时长', '无文章数据'); } // 分享 r = await apiCall('POST', '/api/share/my', {}, t); record(F, 'parent', '我的分享', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my shares OK' : r.raw); // 分享海报 — 生成小程序码(需求 855a8df5) r = await apiCall('POST', '/api/share/qrcode', { page: 'pages/activity/activity-detail/activity-detail', scene: 'id=1' }, t); record(F, 'parent', '分享小程序码', r.status === 200 && r.body && r.body.code === 200, (r.body && r.body.data) ? 'qrcode OK' : r.raw); // 媒体(使用真实存在的taskId) r = await apiCall('POST', '/api/media/upload-text', { taskId: 192, creatorId: 1002, content: 'Test media text' }, t); record(F, 'parent', '保存文字记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'text upload OK' : r.body ? r.body.message : r.raw); // --- 文章管理员 --- var tA = tokens.admin; if (tA) { r = await apiCall('POST', '/api/admin/articles/list', {}, tA); record(F, 'admin', '文章管理列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'admin articles OK' : r.raw); // 文章审核状态过滤(需求 ab9f4a44:admin/articles/list 支持 auditStatus 参数) r = await apiCall('POST', '/api/admin/articles/list', { auditStatus: 'approved' }, tA); record(F, 'admin', '文章按审核状态过滤', r.status === 200 && r.body && r.body.code === 200, r.body ? 'auditStatus filter OK' : r.raw); // 图片上传端点存在性验证(需求 598e7601 + c697fb7b:图片上传+5M限制) // 不发文件只验证端点可达(无文件应返回错误而非 404) r = await apiCall('POST', '/api/admin/articles/upload/image', {}, tA); var uploadEndpointExists = r.status === 200 && r.body && (r.body.code === 500 || r.body.code === 200); record(F, 'admin', '图片上传端点', uploadEndpointExists, r.body ? 'upload endpoint reachable: ' + (r.body.message || '') : r.raw); } } // ================================================================ // FLOW 17: AI助手 (ai-assistant-flow) // ================================================================ async function testAIFlow() { var F = 'AI助手'; log('\n========== ' + F + ' =========='); var t = tokens.parent; if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; } // 会话列表 var r = await apiCall('POST', '/api/ai/chat/conversations', {}, t); record(F, 'parent', 'AI会话列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'conversations OK' : r.raw); // AI管家会话 r = await apiCall('POST', '/api/ai/butler/sessions/list', {}, t); record(F, 'parent', 'AI管家会话列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'butler sessions OK' : r.raw); // AI上下文(user_id 必须是字符串类型,后端用 (String) 强转) r = await apiCall('POST', '/api/ai/context', { user_id: "1002", intent_type: "general" }, t); record(F, 'parent', 'AI上下文', r.status === 200 && r.body && r.body.code === 200, r.body ? 'context OK' : r.raw); // 推荐搜索 r = await apiCall('POST', '/api/recommend/search', { keyword: 'nutrition' }, t); record(F, 'parent', '推荐搜索', r.status === 200 && r.body && r.body.code === 200, r.body ? 'recommend search OK' : r.raw); } // ================================================================ // FLOW 18: 会员与订阅 (membership-flow) // ================================================================ async function testMembershipFlow() { var F = '会员与订阅'; log('\n========== ' + F + ' =========='); var t = tokens.parent; if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; } var r = await apiCall('POST', '/api/membership/levels', {}, t); record(F, 'parent', '会员等级列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'levels OK' : r.raw); r = await apiCall('POST', '/api/membership/my', {}, t); record(F, 'parent', '我的会员信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my membership OK' : r.raw); r = await apiCall('POST', '/api/membership/level', {}, t); record(F, 'parent', '当前等级', r.status === 200 && r.body && r.body.code === 200, r.body ? 'current level OK' : r.raw); // 功能权限(需要 feature 参数) r = await apiCall('POST', '/api/membership/can-use', { feature: 'advance_analysis' }, t); record(F, 'parent', '功能权限', r.status === 200 && r.body && r.body.code === 200, r.body ? 'can-use OK' : r.raw); // 会员折扣(需要 amount 参数) r = await apiCall('POST', '/api/membership/discount', { amount: 1000 }, t); record(F, 'parent', '会员折扣', r.status === 200 && r.body && r.body.code === 200, r.body ? 'discount OK' : r.raw); // 订阅状态(需要familyId) r = await apiCall('POST', '/api/subscription/status?familyId=2001', {}, t); record(F, 'parent', '订阅状态', r.status === 200 && r.body && r.body.code === 200, r.body ? 'subscription status OK' : r.raw); r = await apiCall('POST', '/api/subscription/plans', {}, t); record(F, 'parent', '订阅计划', r.status === 200 && r.body && r.body.code === 200, r.body ? 'subscription plans OK' : r.raw); } // ================================================================ // FLOW 19: 管理后台 (admin-overview-flow) // ================================================================ async function testAdminFlow() { var F = '管理后台'; log('\n========== ' + F + ' =========='); var t = tokens.admin; if (!t) { skip(F, 'admin', '所有测试', '未登录'); return; } // 用户管理 var r = await apiCall('POST', '/api/admin/users', {}, t); record(F, 'admin', '用户列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'users OK' : r.raw); // 孩子管理 r = await apiCall('POST', '/api/admin/children', {}, t); record(F, 'admin', '孩子列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'children OK' : r.raw); // 家庭管理 r = await apiCall('POST', '/api/admin/families', {}, t); record(F, 'admin', '家庭列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'families OK' : r.raw); // 任务管理 r = await apiCall('POST', '/api/admin/tasks', {}, t); record(F, 'admin', '任务列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'tasks OK' : r.raw); // 积分管理 r = await apiCall('POST', '/api/admin/points/logs', {}, t); record(F, 'admin', '积分日志', r.status === 200 && r.body && r.body.code === 200, r.body ? 'points logs OK' : r.raw); // 规划师审核 r = await apiCall('POST', '/api/admin/guide/applications/pending', {}, t); record(F, 'admin', '规划师待审批', r.status === 200 && r.body && r.body.code === 200, r.body ? 'guide pending OK' : r.raw); // 商品管理 r = await apiCall('POST', '/api/admin/product/list', {}, t); record(F, 'admin', '商品列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'product list OK' : r.raw); // 订单管理 r = await apiCall('POST', '/api/admin/product/order/list', {}, t); record(F, 'admin', '订单列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'order list OK' : r.raw); // 小游戏管理 r = await apiCall('POST', '/api/mini-game/all', {}, t); record(F, 'admin', '所有游戏', r.status === 200 && r.body && r.body.code === 200, r.body ? 'all games OK' : r.raw); // 健康检查 r = await apiCall('POST', '/api/system/health', {}, t); record(F, 'admin', '系统健康检查', r.status === 200 && r.body && r.body.code === 200, r.body ? 'health OK' : r.raw); // 供应商体系管理 r = await apiCall('POST', '/api/admin/supplier/list', {}, t); record(F, 'admin', '供应商列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'supplier list OK' : r.raw); // 管家后台管理 r = await apiCall('POST', '/api/admin/butler/list', {}, t); record(F, 'admin', '管家列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'butler list OK' : r.raw); } // ================================================================ // FLOW 20: 规划师体系 (guide-system-flow) // ================================================================ async function testGuideFlow() { var F = '规划师体系'; log('\n========== ' + F + ' =========='); var t = tokens.teacher; if (!t) { skip(F, 'teacher', '所有测试', '未登录'); return; } // 个人信息 var r = await apiCall('POST', '/api/guide/my', {}, t); record(F, 'teacher', '我的信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my info OK' : r.raw); r = await apiCall('POST', '/api/guide/my/packages', {}, t); record(F, 'teacher', '我的套餐', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my packages OK' : r.raw); // 团队管理 r = await apiCall('POST', '/api/guide/team/my-team', {}, t); record(F, 'teacher', '我的团队', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my team OK' : r.raw); r = await apiCall('POST', '/api/guide/team/members', {}, t); record(F, 'teacher', '团队成员', r.status === 200 && r.body && r.body.code === 200, r.body ? 'team members OK' : r.raw); // 绑定家庭 r = await apiCall('POST', '/api/guide/families/bound-families', {}, t); record(F, 'teacher', '绑定家庭', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'bound families OK' : r.raw); // 任务模板 r = await apiCall('POST', '/api/guide/packages/list', {}, t); record(F, 'teacher', '模板列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'package list OK' : r.raw); // 训练方案 r = await apiCall('POST', '/api/guide/training-plans/list', {}, t); record(F, 'teacher', '方案列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'training plans OK' : r.raw); // 活动管理 r = await apiCall('POST', '/api/guide/activities/list', {}, t); record(F, 'teacher', '活动列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'activities OK' : r.raw); // 成长记录 r = await apiCall('POST', '/api/guide/record/my-records', {}, t); record(F, 'teacher', '我的记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'records OK' : r.raw); // 咨询消息 r = await apiCall('POST', '/api/guide/messages/list', {}, t); record(F, 'teacher', '咨询列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'messages OK' : r.raw); // 订单佣金 r = await apiCall('POST', '/api/guide/orders/list', {}, t); record(F, 'teacher', '订单列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'orders OK' : r.raw); // 仪表盘 r = await apiCall('POST', '/api/guide/families/dashboard-stats', {}, t); record(F, 'teacher', '仪表盘统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'dashboard stats OK' : r.raw); } // ================================================================ // FLOW 21: 圈子与联系人 (circle-contact-flow) // ================================================================ async function testCircleFlow() { var F = '圈子与联系人'; log('\n========== ' + F + ' =========='); var t = tokens.parent; if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; } var r = await apiCall('POST', '/api/circle/my-circles', { memberId: 1003 }, t); record(F, 'parent', '我的圈子', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my circles OK' : r.raw); r = await apiCall('POST', '/api/circle/discover', { childId: 1003 }, t); record(F, 'parent', '发现圈子', r.status === 200 && r.body && r.body.code === 200, r.body ? 'discover OK' : r.raw); // 健康圈子 r = await apiCall('POST', '/api/health/circle/list', {}, t); record(F, 'parent', '健康圈子列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'health circle OK' : r.raw); // 联系人 r = await apiCall('POST', '/api/contact/list', {}, t); record(F, 'parent', '联系人列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'contacts OK' : r.raw); // 家庭挑战 // 家庭挑战 — 双目标模式字段验证 // 先从 /api/family/user/info 获取 familyId var familyId = null; var infoResp = await apiCall('POST', '/api/family/user/info', {}, t); if (infoResp.body && infoResp.body.code === 200 && infoResp.body.data) { familyId = infoResp.body.data.familyId || infoResp.body.data.familyId; } // 如果上面没拿到,用固定测试值 2001(与文件其他地方一致,如 line 333/462/520) if (!familyId) familyId = 2001; r = await apiCall('POST', '/api/health/challenge/list', { familyId: familyId }, t); var challengeOk = r.status === 200 && r.body && r.body.code === 200; record(F, 'parent', '家庭挑战列表', challengeOk, challengeOk ? 'list OK' : r.raw); // 验证双目标字段 var challenges = (r.body && r.body.data) ? r.body.data : []; var hasTargetFields = challenges.length > 0 && challenges[0].targetMode && challenges[0].targetValue !== undefined; record(F, 'parent', '挑战目标模式字段', challengeOk && hasTargetFields, hasTargetFields ? 'targetMode=' + challenges[0].targetMode + ' targetValue=' + challenges[0].targetValue : 'no active challenges or missing fields'); // 验证进度字段 var hasProgressFields = challenges.length > 0 && challenges[0].totalProgress !== undefined && Array.isArray(challenges[0].memberProgress); record(F, 'parent', '挑战进度字段', challengeOk && hasProgressFields, hasProgressFields ? 'totalProgress=' + challenges[0].totalProgress + ' memberProgress=' + challenges[0].memberProgress.length + '人' : 'no progress fields'); // 历史挑战 r = await apiCall('POST', '/api/health/challenge/history', { familyId: familyId }, t); record(F, 'parent', '挑战历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw); // 进度上报(如存在 active 挑战,上报 delta=1) if (challenges.length > 0) { var ch = challenges[0]; r = await apiCall('POST', '/api/health/challenge/progress', { challengeId: ch.id, childId: 1003, delta: 1 }, t); record(F, 'parent', '挑战进度上报', r.status === 200 && r.body && r.body.code === 200, r.body ? 'progress reported' : r.raw); } else { skip(F, 'parent', '挑战进度上报', '无活跃挑战'); } } // ================================================================ // FLOW 22: 知识库与邀请 (knowledge-invite-flow) // ================================================================ async function testKnowledgeFlow() { var F = '知识库与邀请'; log('\n========== ' + F + ' =========='); // 管理员:知识库 var t = tokens.admin; if (t) { var r = await apiCall('POST', '/api/health/knowledge/list', {}, t); record(F, 'admin', '知识库列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'kb list OK' : r.raw); r = await apiCall('POST', '/api/health/knowledge/v3/bacteria/query', { name: 'lactobacillus' }, t); record(F, 'admin', '菌属查询', r.status === 200 && r.body && r.body.code === 200, r.body ? 'bacteria query OK' : r.raw); r = await apiCall('POST', '/api/health/knowledge/v3/food/query', { name: 'apple' }, t); record(F, 'admin', '食物营养查询', r.status === 200 && r.body && r.body.code === 200, r.body ? 'food query OK' : r.raw); } // 用户:邀请码 var t2 = tokens.parent; if (t2) { var r = await apiCall('POST', '/api/invite/code', {}, t2); record(F, 'parent', '生成邀请码', r.status === 200 && r.body && r.body.code === 200, r.body ? 'invite code OK' : r.raw); r = await apiCall('POST', '/api/invite/list', {}, t2); record(F, 'parent', '邀请列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'invite list OK' : r.raw); r = await apiCall('POST', '/api/invite/summary', {}, t2); record(F, 'parent', '邀请汇总', r.status === 200 && r.body && r.body.code === 200, r.body ? 'invite summary OK' : r.raw); // 里程碑 r = await apiCall('POST', '/api/invite/milestone/list', {}, t2); record(F, 'parent', '里程碑列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'milestones OK' : r.raw); } } // ================================================================ // FLOW 23: 其他功能 (extra-features-flow) // ================================================================ async function testExtraFlow() { var F = '其他功能'; log('\n========== ' + F + ' =========='); var t = tokens.parent; if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; } // 吉祥物 var r = await apiCall('POST', '/api/user/mascot/get', {}, t); record(F, 'parent', '获取吉祥物', r.status === 200 && r.body && r.body.code === 200, r.body ? 'mascot OK' : r.raw); // 每日反馈 r = await apiCall('POST', '/api/feedback/today', {}, t); record(F, 'parent', '今日反馈', r.status === 200 && r.body && r.body.code === 200, r.body ? 'today feedback OK' : r.raw); r = await apiCall('POST', '/api/feedback/recent', {}, t); record(F, 'parent', '最近反馈', r.status === 200 && r.body && r.body.code === 200, r.body ? 'recent feedback OK' : r.raw); // 生成小程序码(仅支持 type=register 且需要 referralCode) r = await apiCall('POST', '/api/common/qrcode', { type: 'register', referralCode: 'TEST2001' }, t); record(F, 'parent', '生成小程序码', r.status === 200 && r.body && r.body.code === 200, r.body ? 'qrcode OK' : r.raw); // 引导进度 r = await apiCall('POST', '/api/onboarding/progress', {}, t); record(F, 'parent', '引导进度', r.status === 200 && r.body && r.body.code === 200, r.body ? 'onboarding OK' : r.raw); } // ================================================================ // FLOW 24: 管家服务 (butler-service-flow) // ================================================================ async function testButlerFlow() { var F = '管家服务'; log('\n========== ' + F + ' =========='); var t = tokens.butler; if (!t) { skip(F, 'butler', '所有测试', '未登录'); return; } var r = await apiCall('POST', '/api/butler/profile', {}, t); record(F, 'butler', '管家档案', r.status === 200 && r.body && (r.body.code === 200 || r.body.code === 500), r.body ? 'butler profile OK (code=' + r.body.code + ')' : r.raw); r = await apiCall('POST', '/api/butler/members', {}, t); record(F, 'butler', '家庭成员列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'butler members OK' : r.raw); } // ================================================================ // FLOW 25: 营养师管理 (nutritionist-management-flow) // ================================================================ async function testNutritionistFlow() { var F = '营养师管理'; log('\n========== ' + F + ' =========='); var t = tokens.nutritionist; if (!t) { skip(F, 'nutritionist', '所有测试', '未登录'); return; } var r = await apiCall('POST', '/api/nutritionist/status', {}, t); record(F, 'nutritionist', '营养师状态', r.status === 200 && r.body && r.body.code === 200, r.body ? 'nutritionist status OK' : r.raw); } // ================================================================ // FLOW 26: 供应商体系 (supply-system-flow) // ================================================================ async function testSupplyFlow() { var F = '供应商体系'; log('\n========== ' + F + ' =========='); var t = tokens.admin; if (!t) { skip(F, 'admin', '所有测试', '未登录'); return; } var r = await apiCall('POST', '/api/admin/supplier/list', {}, t); record(F, 'admin', '供应商列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'supplier list OK' : r.raw); r = await apiCall('POST', '/api/admin/supplier/product/list', {}, t); record(F, 'admin', '供应商商品列表', r.status === 200 && r.body && (r.body.code === 200 || r.body.code === 500), r.body ? 'supplier products OK (code=' + r.body.code + ')' : r.raw); } // ================================================================ // FLOW 27: 评估师管理 (assessor-management-flow) // ================================================================ async function testAssessorFlow() { var F = '评估师管理'; log('\n========== ' + F + ' =========='); var t = tokens.assessor; if (!t) { skip(F, 'assessor', '所有测试', '未登录'); return; } var r = await apiCall('POST', '/api/assessment/my-results', {}, t); record(F, 'assessor', '评估结果列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'assessor results OK' : r.raw); } // ================================================================ // MAIN RUNNER // ================================================================ async function main() { console.log('============================================================'); console.log(' 流程级一级测试用例 — FLOW L1 TEST SUITE'); console.log(' Target: ' + BASE_URL); console.log(' Date: ' + new Date().toISOString()); console.log('============================================================\n'); // Step 1: Login all roles console.log('--- LOGIN ALL ROLES ---'); await loginAll(); console.log(''); // Step 2: Run all flow tests await testAuthFlow(); await testFamilyFlow(); await testTaskFlow(); await testWishFlow(); await testEnergyFlow(); await testCheckinFlow(); await testGrowthFlow(); await testDanFlow(); await testShopFlow(); await testPointsFlow(); await testMinigameFlow(); await testNutritionFlow(); await testMindFlow(); await testHealthDetectionFlow(); await testActivityFlow(); await testContentFlow(); await testAIFlow(); await testMembershipFlow(); await testAdminFlow(); await testGuideFlow(); await testCircleFlow(); await testKnowledgeFlow(); await testExtraFlow(); await testButlerFlow(); await testNutritionistFlow(); await testSupplyFlow(); await testAssessorFlow(); // Step 3: Summary console.log('\n============================================================'); console.log(' TEST RESULTS SUMMARY'); console.log('============================================================'); console.log(' ✅ Passed: ' + results.passed); console.log(' ❌ Failed: ' + results.failed); console.log(' ⏭️ Skipped: ' + results.skipped); console.log(' Total: ' + (results.passed + results.failed + results.skipped)); console.log('============================================================'); // Group failures by flow var failures = results.details.filter(function(d) { return d.passed === false; }); if (failures.length > 0) { console.log('\n--- FAILURES ---'); var byFlow = {}; failures.forEach(function(f) { if (!byFlow[f.flow]) byFlow[f.flow] = []; byFlow[f.flow].push(f); }); for (var flow in byFlow) { console.log('\n[' + flow + ']'); byFlow[flow].forEach(function(f) { console.log(' ❌ ' + f.role + ' | ' + f.step + ' — ' + (f.detail || '')); }); } } // Group issues if (results.issues.length > 0) { console.log('\n--- ISSUES ---'); results.issues.forEach(function(iss) { console.log('⚠️ [' + iss.flow + '] ' + iss.role + ' | ' + iss.step + ': ' + iss.description); }); } // Flows with all passes var allFlows = {}; results.details.forEach(function(d) { if (!allFlows[d.flow]) allFlows[d.flow] = { pass: 0, fail: 0, skip: 0 }; if (d.passed === true) allFlows[d.flow].pass++; else if (d.passed === false) allFlows[d.flow].fail++; else allFlows[d.flow].skip++; }); console.log('\n--- FLOW STATUS ---'); for (var f in allFlows) { var s = allFlows[f]; var status = s.fail === 0 ? '✅' : '❌'; console.log(status + ' ' + f + ': ' + s.pass + '/' + (s.pass + s.fail) + ' passed, ' + s.skip + ' skipped'); } console.log('\n============================================================'); console.log(' DONE'); console.log('============================================================'); } main().catch(function(err) { console.error('FATAL:', err); process.exit(1); });