flow-l1-tests.js 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423
  1. /**
  2. * ================================================================
  3. * FLOW-LEVEL L1 TEST SUITE — 流程级一级测试用例
  4. * ================================================================
  5. * Based on docs/flows/*.md flow diagrams
  6. * Tests each flow's key endpoints per role
  7. *
  8. * Roles: parent, child, teacher, admin, vendor
  9. *
  10. * Usage:
  11. * node tests/e2e/flow-l1-tests.js
  12. * ================================================================
  13. */
  14. var http = require('http');
  15. var https = require('https');
  16. var querystring = require('querystring');
  17. var path = require('path');
  18. var child_process = require('child_process');
  19. // ================================================================
  20. // Configuration
  21. // ================================================================
  22. var BASE_URL = process.env.PW_BASE_URL || 'http://cfc.iwintrue.com';
  23. var MYSQL_HOST = process.env.MYSQL_HOST || '192.168.16.251';
  24. var MYSQL_USER = process.env.MYSQL_USER || 'zxyj';
  25. var MYSQL_PASS = process.env.MYSQL_PASS || 'zxyj@123';
  26. var MYSQL_DB = process.env.MYSQL_DB || 'zxyj';
  27. // ================================================================
  28. // Test Results Collector
  29. // ================================================================
  30. var results = {
  31. passed: 0,
  32. failed: 0,
  33. skipped: 0,
  34. issues: [],
  35. details: []
  36. };
  37. // Store last API response for debug output on failure
  38. var lastApiResponse = null;
  39. function log(msg) {
  40. console.log('[TEST] ' + msg);
  41. }
  42. function record(flow, role, step, passed, detail) {
  43. var icon = passed ? '✅' : '❌';
  44. var line = icon + ' [' + flow + '] ' + role + ' | ' + step + (detail ? ' — ' + detail : '');
  45. if (!passed && lastApiResponse) {
  46. // Show actual error response for debugging
  47. var respPreview = JSON.stringify(lastApiResponse).substring(0, 300);
  48. line += ' | RESP: ' + respPreview;
  49. }
  50. console.log(line);
  51. results.details.push({ flow: flow, role: role, step: step, passed: passed, detail: detail });
  52. if (passed) results.passed++;
  53. else results.failed++;
  54. }
  55. function skip(flow, role, step, reason) {
  56. var line = '⏭️ [' + flow + '] ' + role + ' | ' + step + ' — SKIPPED: ' + reason;
  57. console.log(line);
  58. results.details.push({ flow: flow, role: role, step: step, passed: null, detail: 'SKIP: ' + reason });
  59. results.skipped++;
  60. }
  61. function issue(flow, role, step, desc) {
  62. results.issues.push({ flow: flow, role: role, step: step, description: desc });
  63. }
  64. // ================================================================
  65. // HTTP Request Helper (plain Node.js, no deps)
  66. // ================================================================
  67. function apiCall(method, endpoint, data, token) {
  68. return new Promise(function(resolve) {
  69. var url = new URL(endpoint, BASE_URL);
  70. var options = {
  71. hostname: url.hostname,
  72. port: url.port || (url.protocol === 'https:' ? 443 : 80),
  73. path: url.pathname + url.search,
  74. method: method,
  75. headers: {
  76. 'Content-Type': 'application/json'
  77. },
  78. timeout: 15000
  79. };
  80. if (token) {
  81. options.headers['Authorization'] = 'Bearer ' + token;
  82. }
  83. var body = data ? JSON.stringify(data) : null;
  84. if (body) {
  85. options.headers['Content-Length'] = Buffer.byteLength(body);
  86. }
  87. var transport = url.protocol === 'https:' ? https : http;
  88. var req = transport.request(options, function(res) {
  89. var chunks = [];
  90. res.on('data', function(chunk) { chunks.push(chunk); });
  91. res.on('end', function() {
  92. var rawBody = Buffer.concat(chunks).toString();
  93. var parsed = null;
  94. try { parsed = JSON.parse(rawBody); } catch(e) { parsed = null; }
  95. lastApiResponse = parsed || rawBody;
  96. resolve({
  97. status: res.statusCode,
  98. body: parsed,
  99. raw: rawBody
  100. });
  101. });
  102. });
  103. req.on('error', function(err) {
  104. resolve({ status: 0, body: null, raw: err.message });
  105. });
  106. req.on('timeout', function() {
  107. req.destroy();
  108. resolve({ status: 0, body: null, raw: 'TIMEOUT' });
  109. });
  110. if (body) req.write(body);
  111. req.end();
  112. });
  113. }
  114. // ================================================================
  115. // DB Helper - insert verification codes
  116. // ================================================================
  117. function insertCode(phone) {
  118. var type = phone === '13800138000' ? 'admin_login' : 'login';
  119. try {
  120. child_process.execSync(
  121. 'mysql -h ' + MYSQL_HOST + ' -u ' + MYSQL_USER + ' -p"' + MYSQL_PASS + '" ' + MYSQL_DB +
  122. ' -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);"',
  123. { stdio: 'ignore', shell: true, timeout: 10000 }
  124. );
  125. } catch(e) {}
  126. }
  127. // ================================================================
  128. // Login Helper
  129. // ================================================================
  130. var tokens = {};
  131. async function loginAll() {
  132. var roles = {
  133. parent: { phone: '13701366188', method: 'code' },
  134. child: { phone: '13701366189', method: 'code' },
  135. teacher: { phone: '13800000001', method: 'code' },
  136. admin: { phone: '13800138000', method: 'password' },
  137. vendor: { phone: '13800138001', method: 'code' }
  138. };
  139. for (var role in roles) {
  140. var cfg = roles[role];
  141. insertCode(cfg.phone);
  142. insertCode(cfg.phone);
  143. insertCode(cfg.phone);
  144. var resp;
  145. if (cfg.method === 'password') {
  146. resp = await apiCall('POST', '/api/admin-auth/login-by-password', {
  147. phone: cfg.phone,
  148. password: 'admin123'
  149. });
  150. } else {
  151. resp = await apiCall('POST', '/api/admin-auth/login', {
  152. phone: cfg.phone,
  153. code: '123456'
  154. });
  155. }
  156. if (resp.body && resp.body.code === 200 && resp.body.data && resp.body.data.token) {
  157. tokens[role] = resp.body.data.token;
  158. log('Login ' + role + ' OK');
  159. } else {
  160. log('Login ' + role + ' FAILED: ' + (resp.body ? resp.body.message : resp.raw));
  161. }
  162. }
  163. }
  164. // ================================================================
  165. // TEST FLOWS
  166. // ================================================================
  167. // ================================================================
  168. // FLOW 1: 认证与用户管理 (user-auth-flow)
  169. // ================================================================
  170. async function testAuthFlow() {
  171. var F = '认证与用户管理';
  172. log('\n========== ' + F + ' ==========');
  173. // --- 家长 ---
  174. var t = tokens.parent;
  175. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  176. var r = await apiCall('POST', '/api/auth/verify', {}, t);
  177. record(F, 'parent', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  178. r = await apiCall('POST', '/api/user/info', {}, t);
  179. record(F, 'parent', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw);
  180. r = await apiCall('POST', '/api/family/user/roles', {}, t);
  181. 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);
  182. // 切换到孩子视图(switch-role需要用户已拥有该角色,家长切孩子视图应使用 member/switch)
  183. r = await apiCall('POST', '/api/family/member/switch', { memberId: 1003 }, t);
  184. record(F, 'parent', '切换到孩子视图', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  185. r = await apiCall('POST', '/api/family/user/switch-back-to-parent', {}, t);
  186. record(F, 'parent', '切换回家长', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  187. // --- 孩子 ---
  188. t = tokens.child;
  189. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  190. r = await apiCall('POST', '/api/auth/verify', {}, t);
  191. record(F, 'child', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  192. r = await apiCall('POST', '/api/user/info', {}, t);
  193. record(F, 'child', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw);
  194. // --- 教师/规划师 ---
  195. t = tokens.teacher;
  196. if (!t) { skip(F, 'teacher', '所有测试', '未登录'); return; }
  197. r = await apiCall('POST', '/api/auth/verify', {}, t);
  198. record(F, 'teacher', 'JWT验证', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  199. r = await apiCall('POST', '/api/user/info', {}, t);
  200. record(F, 'teacher', '获取用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'userId=' + (r.body.data && r.body.data.id) : r.raw);
  201. // --- 管理员 ---
  202. t = tokens.admin;
  203. if (!t) { skip(F, 'admin', '所有测试', '未登录'); return; }
  204. r = await apiCall('POST', '/api/admin-auth/info', {}, t);
  205. record(F, 'admin', '管理员信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'admin OK' : r.raw);
  206. }
  207. // ================================================================
  208. // FLOW 2: 家庭管理 (family-management-flow)
  209. // ================================================================
  210. async function testFamilyFlow() {
  211. var F = '家庭管理';
  212. log('\n========== ' + F + ' ==========');
  213. var t = tokens.parent;
  214. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  215. // 用户与角色管理
  216. var r = await apiCall('POST', '/api/family/user/info', {}, t);
  217. record(F, 'parent', '用户信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'info OK' : r.raw);
  218. r = await apiCall('POST', '/api/family/user/roles', {}, t);
  219. record(F, 'parent', '角色列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'roles OK' : r.raw);
  220. // 切换到孩子视角(switch-to-child已废弃,使用 /api/family/member/switch)
  221. r = await apiCall('POST', '/api/family/member/switch', { memberId: 1003 }, t);
  222. record(F, 'parent', '切换到孩子视图', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  223. r = await apiCall('POST', '/api/family/user/switch-back-to-parent', {}, t);
  224. record(F, 'parent', '切换回家长', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  225. // 家庭成员管理
  226. r = await apiCall('POST', '/api/family/member/list', {}, t);
  227. 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);
  228. r = await apiCall('POST', '/api/family/user/children/list', {}, t);
  229. 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);
  230. r = await apiCall('POST', '/api/family/user/family-members', {}, t);
  231. record(F, 'parent', '家庭成员列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'familyMembers OK' : r.raw);
  232. // 可见成员
  233. r = await apiCall('POST', '/api/family/user/members/visible', {}, t);
  234. record(F, 'parent', '可见成员', r.status === 200 && r.body && r.body.code === 200, r.body ? 'visible OK' : r.raw);
  235. // 邀请码(invite-code已废弃,使用 /api/family/invite/qrcode)
  236. r = await apiCall('POST', '/api/family/invite/qrcode', {}, t);
  237. 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);
  238. // 家庭关系问卷(需要memberId)
  239. r = await apiCall('POST', '/api/family/questionnaire/generate', { memberId: 1003 }, t);
  240. record(F, 'parent', 'AI生成问卷', r.status === 200 && r.body && r.body.code === 200, r.body ? 'questionnaire OK' : r.body ? r.body.message : r.raw);
  241. // 关系质量(需要familyId)
  242. r = await apiCall('POST', '/api/family/relationship/scores', { familyId: 2001 }, t);
  243. record(F, 'parent', '关系质量评分', r.status === 200 && r.body && r.body.code === 200, r.body ? 'scores OK' : r.raw);
  244. // 互动记录
  245. r = await apiCall('POST', '/api/family/interaction/types', {}, t);
  246. record(F, 'parent', '互动类型', r.status === 200 && r.body && r.body.code === 200, r.body ? 'types OK' : r.raw);
  247. r = await apiCall('POST', '/api/family/interaction/list', {}, t);
  248. record(F, 'parent', '互动列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'interaction list OK' : r.raw);
  249. // 规划师绑定
  250. r = await apiCall('POST', '/api/family/teacher-bindings/pending', {}, t);
  251. record(F, 'parent', '待审批绑定', r.status === 200 && r.body && r.body.code === 200, r.body ? 'pending bindings OK' : r.raw);
  252. r = await apiCall('POST', '/api/family/teacher-bindings/my-requests', {}, t);
  253. record(F, 'parent', '我的请求', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my requests OK' : r.raw);
  254. }
  255. // ================================================================
  256. // FLOW 3: 任务管理 (task-management-flow)
  257. // ================================================================
  258. async function testTaskFlow() {
  259. var F = '任务管理';
  260. log('\n========== ' + F + ' ==========');
  261. // --- 家长:创建任务 ---
  262. var t = tokens.parent;
  263. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  264. var r = await apiCall('POST', '/api/tasks/create', {
  265. childId: 1003,
  266. title: 'TEST-Flow-ReadBook',
  267. description: 'Reading test flow book',
  268. category: 'study',
  269. points: 10,
  270. requireReview: true
  271. }, t);
  272. var taskId = (r.body && r.body.code === 200 && r.body.data) ? r.body.data.id || r.body.data.taskId : null;
  273. record(F, 'parent', '创建任务', r.status === 200 && r.body && r.body.code === 200, taskId ? 'taskId=' + taskId : r.body ? r.body.message : r.raw);
  274. // 家长今日任务
  275. r = await apiCall('POST', '/api/tasks/today-parent', {}, t);
  276. record(F, 'parent', '家长今日任务', r.status === 200 && r.body && r.body.code === 200, r.body ? 'today-parent OK' : r.raw);
  277. // 可选小游戏
  278. r = await apiCall('POST', '/api/tasks/minigame-options', {}, t);
  279. record(F, 'parent', '可选小游戏', r.status === 200 && r.body && r.body.code === 200, r.body ? 'minigame options OK' : r.raw);
  280. // --- 孩子:执行任务 ---
  281. var t2 = tokens.child;
  282. if (!t2) { skip(F, 'child', '所有测试', '未登录'); return; }
  283. r = await apiCall('POST', '/api/tasks/today', { childId: 1003 }, t2);
  284. var tasks = (r.body && r.body.code === 200 && r.body.data) ? (Array.isArray(r.body.data) ? r.body.data : (r.body.data.tasks || [])) : [];
  285. record(F, 'child', '今日任务列表', r.status === 200 && r.body && r.body.code === 200, 'count=' + tasks.length);
  286. // 找到刚创建的任务或任意待完成任务
  287. var targetTask = null;
  288. for (var i = 0; i < tasks.length; i++) {
  289. if (tasks[i].status === 'pending' || tasks[i].status === 'active') {
  290. targetTask = tasks[i];
  291. break;
  292. }
  293. }
  294. if (targetTask) {
  295. r = await apiCall('POST', '/api/tasks/' + targetTask.id + '/complete', { evidence: 'Test complete' }, t2);
  296. record(F, 'child', '完成任务', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  297. } else {
  298. skip(F, 'child', '完成任务', '无可用待完成任务');
  299. }
  300. // 任务历史(需要childId)
  301. r = await apiCall('POST', '/api/tasks/history', { childId: 1003 }, t2);
  302. record(F, 'child', '任务历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw);
  303. // --- 家长:审核 ---
  304. r = await apiCall('POST', '/api/tasks/pending-review', {}, t);
  305. var pending = (r.body && r.body.code === 200 && r.body.data) ? (Array.isArray(r.body.data) ? r.body.data : (r.body.data.tasks || [])) : [];
  306. record(F, 'parent', '待审核列表', r.status === 200 && r.body && r.body.code === 200, 'pending=' + pending.length);
  307. if (pending.length > 0) {
  308. var reviewTask = pending[0];
  309. var reviewId = reviewTask.id || reviewTask.taskId;
  310. r = await apiCall('POST', '/api/tasks/' + reviewId + '/review', { approved: true }, t);
  311. record(F, 'parent', '审核通过任务', r.status === 200 && r.body && r.body.code === 200, r.body ? 'review approved OK' : r.raw);
  312. }
  313. // 任务提醒
  314. r = await apiCall('POST', '/api/task-reminders/upcoming/1003', {}, t);
  315. record(F, 'parent', '即将到期任务', r.status === 200 && r.body && r.body.code === 200, r.body ? 'upcoming OK' : r.raw);
  316. r = await apiCall('POST', '/api/task-reminders/overdue/1003', {}, t);
  317. record(F, 'parent', '逾期任务', r.status === 200 && r.body && r.body.code === 200, r.body ? 'overdue OK' : r.raw);
  318. r = await apiCall('POST', '/api/task-reminders/statistics/1003', {}, t);
  319. record(F, 'parent', '任务统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'stats OK' : r.raw);
  320. // 清理:删除测试任务
  321. if (taskId) {
  322. await apiCall('POST', '/api/tasks/' + taskId + '/delete', {}, t);
  323. }
  324. }
  325. // ================================================================
  326. // FLOW 4: 心愿管理 (wish-management-flow)
  327. // ================================================================
  328. async function testWishFlow() {
  329. var F = '心愿管理';
  330. log('\n========== ' + F + ' ==========');
  331. // --- 孩子:创建心愿 ---
  332. var t = tokens.child;
  333. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  334. var r = await apiCall('POST', '/api/wishes/create', {
  335. title: 'TEST-Flow-NewBook',
  336. description: 'A new storybook',
  337. category: 'education'
  338. }, t);
  339. var wishId = (r.body && r.body.code === 200 && r.body.data) ? (r.body.data.id || r.body.data.wishId) : null;
  340. record(F, 'child', '创建心愿', r.status === 200 && r.body && r.body.code === 200, wishId ? 'wishId=' + wishId : r.body ? r.body.message : r.raw);
  341. // --- 家长:审批 ---
  342. var t2 = tokens.parent;
  343. if (!t2) { skip(F, 'parent', '所有测试', '未登录'); return; }
  344. r = await apiCall('POST', '/api/wishes/pending', { familyId: 2001 }, t2);
  345. 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);
  346. if (wishId) {
  347. r = await apiCall('POST', '/api/wishes/' + wishId + '/set-price', { price: 50 }, t2);
  348. record(F, 'parent', '家长定价', r.status === 200 && r.body && r.body.code === 200, r.body ? 'price set OK' : r.body ? r.body.message : r.raw);
  349. // 孩子申请兑换
  350. r = await apiCall('POST', '/api/wishes/' + wishId + '/exchange', {}, t);
  351. record(F, 'child', '申请兑换', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  352. // 家长审批兑换
  353. r = await apiCall('POST', '/api/wishes/' + wishId + '/approve-exchange', {}, t2);
  354. record(F, 'parent', '审批兑换', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  355. // 家长标记已购买
  356. r = await apiCall('POST', '/api/wishes/' + wishId + '/fulfill', {}, t2);
  357. record(F, 'parent', '标记已购买', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  358. // 孩子确认收货
  359. r = await apiCall('POST', '/api/wishes/' + wishId + '/confirm', {}, t);
  360. record(F, 'child', '确认收货', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  361. }
  362. // 心愿列表
  363. r = await apiCall('POST', '/api/wishes/list', {}, t);
  364. 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);
  365. // 心愿详情
  366. if (wishId) {
  367. r = await apiCall('POST', '/api/wishes/' + wishId, {}, t);
  368. record(F, 'child', '心愿详情', r.status === 200 && r.body && r.body.code === 200, r.body ? 'detail OK' : r.raw);
  369. }
  370. }
  371. // ================================================================
  372. // FLOW 5: 五维能量系统 (energy-system-flow)
  373. // ================================================================
  374. async function testEnergyFlow() {
  375. var F = '五维能量';
  376. log('\n========== ' + F + ' ==========');
  377. var t = tokens.parent;
  378. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  379. var r = await apiCall('POST', '/api/energy/overview', { childId: 1003 }, t);
  380. record(F, 'parent', '五维概览', r.status === 200 && r.body && r.body.code === 200, r.body ? 'overview OK' : r.raw);
  381. r = await apiCall('POST', '/api/energy/sandbox', {}, t);
  382. record(F, 'parent', '能量沙盘', r.status === 200 && r.body && r.body.code === 200, r.body ? 'sandbox OK' : r.raw);
  383. r = await apiCall('POST', '/api/energy/logs', { childId: 1003 }, t);
  384. record(F, 'parent', '能量流水', r.status === 200 && r.body && r.body.code === 200, r.body ? 'logs OK' : r.raw);
  385. r = await apiCall('POST', '/api/energy/score-history', { memberId: 1003, memberType: 'child' }, t);
  386. record(F, 'parent', '分数历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw);
  387. r = await apiCall('POST', '/api/energy/family-score-history', { familyId: 2001 }, t);
  388. record(F, 'parent', '家庭五维聚合', r.status === 200 && r.body && r.body.code === 200, r.body ? 'family score OK' : r.raw);
  389. // 能量发放(需要childId + taskId + amount)先创建任务获取真实taskId
  390. var r2 = await apiCall('POST', '/api/tasks/create', {
  391. childId: 1003,
  392. title: 'TEST-Energy-Grant',
  393. description: 'Energy grant test',
  394. category: 'study',
  395. points: 10,
  396. requireReview: false
  397. }, t);
  398. var realTaskId = (r2.body && r2.body.code === 200 && r2.body.data) ? r2.body.data : 1;
  399. r = await apiCall('POST', '/api/energy/grant', { childId: 1003, taskId: realTaskId, amount: 5, reason: 'Test grant' }, t);
  400. record(F, 'parent', '发放能量', r.status === 200 && r.body && r.body.code === 200, r.body ? 'grant OK' : r.body ? r.body.message : r.raw);
  401. // 孩子维度
  402. var t2 = tokens.child;
  403. if (t2) {
  404. r = await apiCall('POST', '/api/energy/overview', { childId: 1003 }, t2);
  405. record(F, 'child', '五维概览', r.status === 200 && r.body && r.body.code === 200, r.body ? 'child overview OK' : r.raw);
  406. }
  407. // 维度管理(需要memberId)
  408. r = await apiCall('POST', '/api/dimension/overview', { memberId: 1003 }, t);
  409. record(F, 'parent', '维度概览', r.status === 200 && r.body && r.body.code === 200, r.body ? 'dimension overview OK' : r.raw);
  410. // 年度能量(需要birthYear/birthMonth/birthDay作为查询参数)
  411. r = await apiCall('POST', '/api/zodiac/energy?birthYear=2018&birthMonth=5&birthDay=15', {}, t);
  412. record(F, 'parent', '年度五维能量', r.status === 200 && r.body && r.body.code === 200, r.body ? 'zodiac energy OK' : r.raw);
  413. // 家庭收入
  414. r = await apiCall('POST', '/api/earnings/family/summary', {}, t);
  415. record(F, 'parent', '家庭收入汇总', r.status === 200 && r.body && r.body.code === 200, r.body ? 'earnings summary OK' : r.raw);
  416. }
  417. // ================================================================
  418. // FLOW 6: 健康打卡 (health-checkin-flow)
  419. // ================================================================
  420. async function testCheckinFlow() {
  421. var F = '健康打卡';
  422. log('\n========== ' + F + ' ==========');
  423. var t = tokens.child;
  424. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  425. // 综合打卡
  426. var r = await apiCall('POST', '/api/daily/checkin/create', {
  427. type: 'comprehensive',
  428. mood: 'happy',
  429. energy: 8,
  430. note: 'Test checkin'
  431. }, t);
  432. record(F, 'child', '综合打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  433. // 运动打卡
  434. r = await apiCall('POST', '/api/health/exercise/create', {
  435. exerciseType: 'running',
  436. duration: 30,
  437. note: 'Test run'
  438. }, t);
  439. record(F, 'child', '运动打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'exercise OK' : r.body ? r.body.message : r.raw);
  440. // 饮食打卡(HealthMealRecord实体,需要memberId + mealType + foodItems)
  441. r = await apiCall('POST', '/api/health/meal/create', {
  442. memberId: 1003,
  443. mealType: 'lunch',
  444. foodItems: 'rice,vegetables',
  445. remark: 'Test meal'
  446. }, t);
  447. record(F, 'child', '饮食打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'meal OK' : r.body ? r.body.message : r.raw);
  448. // 睡眠打卡(HealthSleepRecord实体,需要memberId + sleepTime + wakeTime)
  449. r = await apiCall('POST', '/api/health/sleep/create', {
  450. memberId: 1003,
  451. sleepTime: '2026-07-27T22:00:00',
  452. wakeTime: '2026-07-28T07:00:00',
  453. durationMinutes: 540,
  454. qualityScore: 8,
  455. remark: 'Good sleep'
  456. }, t);
  457. record(F, 'child', '睡眠打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'sleep OK' : r.body ? r.body.message : r.raw);
  458. // 喝水打卡
  459. r = await apiCall('POST', '/api/health/water/create', {
  460. amount: 2000,
  461. note: 'Drank water'
  462. }, t);
  463. record(F, 'child', '喝水打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'water OK' : r.body ? r.body.message : r.raw);
  464. // 打卡列表(需要memberId参数)
  465. r = await apiCall('POST', '/api/daily/checkin/list', { memberId: 1002 }, t);
  466. record(F, 'child', '打卡列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'list OK' : r.raw);
  467. // 打卡统计
  468. r = await apiCall('POST', '/api/health/checkin/stats', {}, t);
  469. record(F, 'child', '打卡统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'stats OK' : r.raw);
  470. // 打卡进度
  471. r = await apiCall('POST', '/api/streak/progress/1003', {}, t);
  472. record(F, 'child', '打卡进度', r.status === 200 && r.body && r.body.code === 200, r.body ? 'streak OK' : r.raw);
  473. // 健康积分
  474. r = await apiCall('POST', '/api/health/score/info', {}, t);
  475. record(F, 'child', '健康积分', r.status === 200 && r.body && r.body.code === 200, r.body ? 'health score OK' : r.raw);
  476. // 健康时间线
  477. r = await apiCall('POST', '/api/health/timeline/list', {}, t);
  478. record(F, 'child', '健康时间线', r.status === 200 && r.body && r.body.code === 200, r.body ? 'timeline OK' : r.raw);
  479. // 健康预警
  480. r = await apiCall('POST', '/api/health/alert/list', {}, t);
  481. record(F, 'child', '健康预警', r.status === 200 && r.body && r.body.code === 200, r.body ? 'alerts OK' : r.raw);
  482. }
  483. // ================================================================
  484. // FLOW 7: 成长档案 (growth-record-flow)
  485. // ================================================================
  486. async function testGrowthFlow() {
  487. var F = '成长档案';
  488. log('\n========== ' + F + ' ==========');
  489. var t = tokens.parent;
  490. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  491. // 创建成长档案
  492. var r = await apiCall('POST', '/api/growth/record/create', {
  493. childId: 1003,
  494. title: 'Test Growth Record',
  495. type: 'assessment',
  496. description: 'Test growth record creation'
  497. }, t);
  498. var recordId = (r.body && r.body.code === 200 && r.body.data) ? (r.body.data.id || r.body.data.recordId) : null;
  499. record(F, 'parent', '创建成长档案', r.status === 200 && r.body && r.body.code === 200, recordId ? 'recordId=' + recordId : r.body ? r.body.message : r.raw);
  500. // 档案列表
  501. r = await apiCall('POST', '/api/growth/record/list', {}, t);
  502. 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);
  503. // 指定孩子档案
  504. r = await apiCall('POST', '/api/growth/record/child/1003', {}, t);
  505. record(F, 'parent', '指定孩子档案', r.status === 200 && r.body && r.body.code === 200, r.body ? 'child records OK' : r.raw);
  506. // 档案详情
  507. if (recordId) {
  508. r = await apiCall('POST', '/api/growth/record/' + recordId, {}, t);
  509. record(F, 'parent', '档案详情', r.status === 200 && r.body && r.body.code === 200, r.body ? 'detail OK' : r.raw);
  510. }
  511. // 生长记录(需要memberId)
  512. r = await apiCall('POST', '/api/health/growth/list', { memberId: 1003 }, t);
  513. record(F, 'parent', '生长记录列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'growth list OK' : r.raw);
  514. r = await apiCall('POST', '/api/health/growth/create', {
  515. childId: 1003,
  516. height: 135.5,
  517. weight: 30.2,
  518. recordedDate: '2026-07-20'
  519. }, t);
  520. record(F, 'parent', '创建生长记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'growth create OK' : r.body ? r.body.message : r.raw);
  521. // 成长计划(GrowthPlan实体,需要planTitle+planContent+durationMonths+status+startDate+endDate)
  522. r = await apiCall('POST', '/api/growth/plan/create', {
  523. childId: 1003,
  524. planTitle: 'Test Growth Plan',
  525. planContent: 'Improve reading skills',
  526. durationMonths: 6,
  527. status: 'active',
  528. startDate: '2026-07-01',
  529. endDate: '2027-01-01'
  530. }, t);
  531. record(F, 'parent', '创建成长计划', r.status === 200 && r.body && r.body.code === 200, r.body ? 'plan create OK' : r.body ? r.body.message : r.raw);
  532. r = await apiCall('POST', '/api/growth/plan/child/1003', {}, t);
  533. record(F, 'parent', '查看孩子计划', r.status === 200 && r.body && r.body.code === 200, r.body ? 'child plans OK' : r.raw);
  534. // 清理
  535. if (recordId) {
  536. await apiCall('POST', '/api/growth/record/' + recordId + '/delete', {}, t);
  537. }
  538. }
  539. // ================================================================
  540. // FLOW 8: DAN测评 (dan-assessment-flow)
  541. // ================================================================
  542. async function testDanFlow() {
  543. var F = 'DAN测评';
  544. log('\n========== ' + F + ' ==========');
  545. var t = tokens.parent;
  546. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  547. // 测评准备
  548. var r = await apiCall('POST', '/api/dan-assessment/material/list', {}, t);
  549. record(F, 'parent', '测评材料列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'materials OK' : r.raw);
  550. r = await apiCall('POST', '/api/dan-assessment/material/active', {}, t);
  551. record(F, 'parent', '启用材料', r.status === 200 && r.body && r.body.code === 200, r.body ? 'active material OK' : r.raw);
  552. r = await apiCall('POST', '/api/dan-assessment/family/config?familyId=2001', {}, t);
  553. record(F, 'parent', '家庭测评配置', r.status === 200 && r.body && r.body.code === 200, r.body ? 'family config OK' : r.raw);
  554. // 额度
  555. r = await apiCall('POST', '/api/assessment/quota/my-list', {}, t);
  556. record(F, 'parent', '可用额度', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'quota list OK' : r.raw);
  557. // 创建测评订单
  558. r = await apiCall('POST', '/api/dan-assessment/order/create', {
  559. childId: 1003,
  560. materialId: 1
  561. }, t);
  562. record(F, 'parent', '创建测评订单', r.status === 200 && r.body && r.body.code === 200, r.body ? r.body.message : r.raw);
  563. // 评估师列表
  564. r = await apiCall('POST', '/api/assessment/appointment/assessor-list', {}, t);
  565. 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);
  566. // 最新测评结果
  567. r = await apiCall('POST', '/api/assessment/latest-result', { childId: 1003 }, t);
  568. record(F, 'parent', '最新测评结果', r.status === 200 && r.body && r.body.code === 200, r.body ? 'latest result OK' : r.raw);
  569. // 测评历史
  570. r = await apiCall('POST', '/api/assessment/history', { childId: 1003 }, t);
  571. record(F, 'parent', '测评历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw);
  572. // 我的报告(只有指导师可以查看,改用teacher token)
  573. var tTeacher = tokens.teacher;
  574. if (tTeacher) {
  575. r = await apiCall('POST', '/api/assessment/my-results', {}, tTeacher);
  576. record(F, 'parent', '我的报告', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my results OK' : r.raw);
  577. } else {
  578. skip(F, 'parent', '我的报告', 'teacher未登录');
  579. }
  580. // 家长自评(需要childId + materialId,"暂无可用测评资料"为业务逻辑非bug)
  581. r = await apiCall('POST', '/api/parent/assessment/create', { childId: 1003, materialId: 1 }, t);
  582. record(F, 'parent', '创建家长自评', r.status === 200 && r.body && r.body.code === 200, r.body ? 'self assessment OK' : r.body ? r.body.message : r.raw);
  583. r = await apiCall('POST', '/api/parent/assessment/records', {}, t);
  584. record(F, 'parent', '自评记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'assessment records OK' : r.raw);
  585. // --- 规划师端 ---
  586. var t2 = tokens.teacher;
  587. if (t2) {
  588. r = await apiCall('POST', '/api/guide/assessment/families', {}, t2);
  589. record(F, 'teacher', '家庭测评列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'guide assessment families OK' : r.raw);
  590. }
  591. }
  592. // ================================================================
  593. // FLOW 9: 商城交易 (shop-flow)
  594. // ================================================================
  595. async function testShopFlow() {
  596. var F = '商城交易';
  597. log('\n========== ' + F + ' ==========');
  598. var t = tokens.parent;
  599. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  600. // 商品浏览
  601. var r = await apiCall('POST', '/api/shop/category/tree', {}, t);
  602. record(F, 'parent', '商品分类树', r.status === 200 && r.body && r.body.code === 200, r.body ? 'category tree OK' : r.raw);
  603. r = await apiCall('POST', '/api/product/list', {}, t);
  604. var products = (r.body && r.body.code === 200 && r.body.data) ? (Array.isArray(r.body.data) ? r.body.data : (r.body.data.records || [])) : [];
  605. record(F, 'parent', '商品列表', r.status === 200 && r.body && r.body.code === 200, 'products=' + products.length);
  606. // 购物车
  607. r = await apiCall('POST', '/api/cart/list', {}, t);
  608. record(F, 'parent', '购物车列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'cart list OK' : r.raw);
  609. r = await apiCall('POST', '/api/cart/count', {}, t);
  610. record(F, 'parent', '购物车数量', r.status === 200 && r.body && r.body.code === 200, r.body ? 'cart count OK' : r.raw);
  611. // 我的订单
  612. r = await apiCall('POST', '/api/product/order/my', {}, t);
  613. record(F, 'parent', '我的订单', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my orders OK' : r.raw);
  614. r = await apiCall('POST', '/api/product/order/my/counts', {}, t);
  615. record(F, 'parent', '订单数量统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'order counts OK' : r.raw);
  616. // 收货地址
  617. r = await apiCall('POST', '/api/consignee/list', {}, t);
  618. record(F, 'parent', '收货地址列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'consignee list OK' : r.raw);
  619. // 售后
  620. r = await apiCall('POST', '/api/shop/after-sales/list', {}, t);
  621. record(F, 'parent', '售后列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'after-sales OK' : r.raw);
  622. // --- 管理员 ---
  623. var t2 = tokens.admin;
  624. if (t2) {
  625. r = await apiCall('POST', '/api/product/my', {}, t2);
  626. record(F, 'admin', '管理端商品列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'admin products OK' : r.raw);
  627. }
  628. }
  629. // ================================================================
  630. // FLOW 10: 积分兑换 (points-exchange-flow)
  631. // ================================================================
  632. async function testPointsFlow() {
  633. var F = '积分兑换';
  634. log('\n========== ' + F + ' ==========');
  635. var t = tokens.child;
  636. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  637. var r = await apiCall('POST', '/api/points/balance', {}, t);
  638. record(F, 'child', '积分余额', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'balance OK' : r.raw);
  639. r = await apiCall('POST', '/api/points/logs', {}, t);
  640. record(F, 'child', '积分流水', r.status === 200 && r.body && r.body.code === 200, r.body ? 'logs OK' : r.raw);
  641. // 兑换商品列表
  642. r = await apiCall('POST', '/api/points/exchange/product/list', {}, t);
  643. record(F, 'child', '兑换商品列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'exchange products OK' : r.raw);
  644. // 奖励系统
  645. r = await apiCall('POST', '/api/rewards/wishlist', {}, t);
  646. record(F, 'child', '奖励清单', r.status === 200 && r.body && r.body.code === 200, r.body ? 'wishlist OK' : r.raw);
  647. r = await apiCall('POST', '/api/rewards/templates', {}, t);
  648. record(F, 'child', '奖励模板', r.status === 200 && r.body && r.body.code === 200, r.body ? 'templates OK' : r.raw);
  649. // 家长端积分
  650. var t2 = tokens.parent;
  651. if (t2) {
  652. r = await apiCall('POST', '/api/points/balance', {}, t2);
  653. record(F, 'parent', '家长积分余额', r.status === 200 && r.body && r.body.code === 200, r.body ? 'parent balance OK' : r.raw);
  654. r = await apiCall('POST', '/api/points/exchange/records', {}, t2);
  655. record(F, 'parent', '兑换记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'exchange records OK' : r.raw);
  656. }
  657. }
  658. // ================================================================
  659. // FLOW 11: 小游戏 (minigame-flow)
  660. // ================================================================
  661. async function testMinigameFlow() {
  662. var F = '小游戏';
  663. log('\n========== ' + F + ' ==========');
  664. var t = tokens.child;
  665. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  666. // 游戏列表
  667. var r = await apiCall('POST', '/api/mini-game/list', {}, t);
  668. record(F, 'child', '小游戏列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'games OK' : r.raw);
  669. // 游戏详情 (Schulte Table)
  670. r = await apiCall('POST', '/api/mini-game/schulte', {}, t);
  671. record(F, 'child', '舒尔特方格详情', r.status === 200 && r.body && r.body.code === 200, r.body ? 'schulte OK' : r.raw);
  672. // 完成游戏(CompleteGameDTO: childId + gameCode + completionTime + score)
  673. r = await apiCall('POST', '/api/mini-game/complete', {
  674. childId: 1003,
  675. gameCode: 'schulte',
  676. score: 95,
  677. completionTime: 30
  678. }, t);
  679. record(F, 'child', '完成游戏', r.status === 200 && r.body && r.body.code === 200, r.body ? 'complete OK' : r.body ? r.body.message : r.raw);
  680. // 游戏历史
  681. r = await apiCall('POST', '/api/game/history', { childId: 1003 }, t);
  682. record(F, 'child', '游戏历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'history OK' : r.raw);
  683. // 最佳成绩
  684. r = await apiCall('POST', '/api/game/best', { childId: 1003 }, t);
  685. record(F, 'child', '最佳成绩', r.status === 200 && r.body && r.body.code === 200, r.body ? 'best OK' : r.raw);
  686. // 排行榜(需要gameCode)
  687. r = await apiCall('POST', '/api/game/leaderboard', { gameCode: 'schulte' }, t);
  688. record(F, 'child', '排行榜', r.status === 200 && r.body && r.body.code === 200, r.body ? 'leaderboard OK' : r.raw);
  689. // 智慧数学
  690. r = await apiCall('POST', '/api/wisdom/math/start', {}, t);
  691. record(F, 'child', '开始数学游戏', r.status === 200 && r.body && r.body.code === 200, r.body ? 'math start OK' : r.raw);
  692. }
  693. // ================================================================
  694. // FLOW 12: 营养与饮食 (nutrition-diet-flow)
  695. // ================================================================
  696. async function testNutritionFlow() {
  697. var F = '营养与饮食';
  698. log('\n========== ' + F + ' ==========');
  699. var t = tokens.parent;
  700. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  701. // 食谱推荐
  702. var r = await apiCall('POST', '/api/meal/recommend', {}, t);
  703. record(F, 'parent', '食谱推荐', r.status === 200 && r.body && r.body.code === 200, r.body ? 'meal recommend OK' : r.raw);
  704. // 饮食日志
  705. r = await apiCall('POST', '/api/meal/logs', {}, t);
  706. record(F, 'parent', '饮食日志', r.status === 200 && r.body && r.body.code === 200, r.body ? 'meal logs OK' : r.raw);
  707. // 营养摄入统计
  708. r = await apiCall('POST', '/api/meal/nutrition-summary', {}, t);
  709. record(F, 'parent', '营养摄入统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'nutrition summary OK' : r.raw);
  710. // 食材推荐指数
  711. r = await apiCall('POST', '/api/food/recommendation/index', {}, t);
  712. record(F, 'parent', '食材推荐指数', r.status === 200 && r.body && r.body.code === 200, r.body ? 'food index OK' : r.raw);
  713. // 营养偏好
  714. r = await apiCall('POST', '/api/nutrition/profile/get', {}, t);
  715. record(F, 'parent', '获取营养偏好', r.status === 200 && r.body && r.body.code === 200, r.body ? 'nutrition profile OK' : r.raw);
  716. // 维度推荐(需要dimensionCode)
  717. r = await apiCall('POST', '/api/recommend/dimension-products', { dimensionCode: 'body' }, t);
  718. record(F, 'parent', '维度推荐商品', r.status === 200 && r.body && r.body.code === 200, r.body ? 'dimension products OK' : r.raw);
  719. // 北京知识库
  720. r = await apiCall('POST', '/api/nutrition/beijing/kb/list', {}, t);
  721. record(F, 'parent', '营养知识库', r.status === 200 && r.body && r.body.code === 200, r.body ? 'kb list OK' : r.raw);
  722. }
  723. // ================================================================
  724. // FLOW 13: 心理与情绪 (mind-emotion-flow)
  725. // ================================================================
  726. async function testMindFlow() {
  727. var F = '心理与情绪';
  728. log('\n========== ' + F + ' ==========');
  729. var t = tokens.child;
  730. if (!t) { skip(F, 'child', '所有测试', '未登录'); return; }
  731. // 情绪打卡(EmotionCheckinDTO - 服务端用userId=child的userId找孩子)
  732. var r = await apiCall('POST', '/api/mind/checkin/create', {
  733. emotion: 'happy',
  734. intensity: 8,
  735. note: 'Feeling good today'
  736. }, t);
  737. record(F, 'child', '情绪打卡', r.status === 200 && r.body && r.body.code === 200, r.body ? 'emotion checkin OK' : r.body ? r.body.message : r.raw);
  738. // 情绪列表(需要childId)
  739. r = await apiCall('POST', '/api/mind/checkin/list', { childId: 1003 }, t);
  740. record(F, 'child', '情绪列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'emotion list OK' : r.raw);
  741. // 最新情绪(需要childId)
  742. r = await apiCall('POST', '/api/mind/checkin/latest', { childId: 1003 }, t);
  743. record(F, 'child', '最新情绪', r.status === 200 && r.body && r.body.code === 200, r.body ? 'latest OK' : r.raw);
  744. // 情绪趋势(需要childId)
  745. r = await apiCall('POST', '/api/mind/checkin/trend', { childId: 1003 }, t);
  746. record(F, 'child', '情绪趋势', r.status === 200 && r.body && r.body.code === 200, r.body ? 'trend OK' : r.raw);
  747. // 情绪统计(需要childId)
  748. r = await apiCall('POST', '/api/mind/checkin/stats', { childId: 1003 }, t);
  749. record(F, 'child', '情绪统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'stats OK' : r.raw);
  750. // 心理测评(需要childId)
  751. r = await apiCall('POST', '/api/mind/screening/history', { childId: 1003 }, t);
  752. record(F, 'child', '筛查历史', r.status === 200 && r.body && r.body.code === 200, r.body ? 'screening history OK' : r.raw);
  753. // 心理运势(需要familyId)
  754. r = await apiCall('POST', '/api/mind/fortune?familyId=2001', {}, t);
  755. record(F, 'child', '心理运势', r.status === 200 && r.body && r.body.code === 200, r.body ? 'fortune OK' : r.raw);
  756. // EMI报告(需要childId)
  757. r = await apiCall('POST', '/api/mind/emireport/latest', { childId: 1003 }, t);
  758. record(F, 'child', 'EMI报告', r.status === 200 && r.body && r.body.code === 200, r.body ? 'EMI report OK' : r.raw);
  759. }
  760. // ================================================================
  761. // FLOW 14: 身体健康检测 (health-detection-flow)
  762. // ================================================================
  763. async function testHealthDetectionFlow() {
  764. var F = '身体健康检测';
  765. log('\n========== ' + F + ' ==========');
  766. var t = tokens.parent;
  767. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  768. var r = await apiCall('POST', '/api/health/report/list', { childId: 1003 }, t);
  769. record(F, 'parent', '健康报告列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'report list OK' : r.raw);
  770. // 先获取最新报告的reportId
  771. r = await apiCall('POST', '/api/health/report/latest', { childId: 1003 }, t);
  772. var reportId = (r.body && r.body.code === 200 && r.body.data) ? (r.body.data.id || r.body.data.reportId) : null;
  773. record(F, 'parent', '最新报告', r.status === 200 && r.body && r.body.code === 200, r.body ? 'latest report OK' : r.raw);
  774. // 先获取有数据的 reportId(如果最新报告无指标,先创建一个测试报告)
  775. r = await apiCall('POST', '/api/health/report/list', { childId: 1003 }, t);
  776. var validReportId = (r.body && r.body.code === 200 && r.body.data && Array.isArray(r.body.data) && r.body.data.length > 0)
  777. ? (r.body.data[0].id || r.body.data[0].reportId) : 1;
  778. r = await apiCall('POST', '/api/health/indicator/list', { reportId: validReportId }, t);
  779. record(F, 'parent', '指标明细', r.status === 200 && r.body && r.body.code === 200, r.body ? 'indicators OK' : r.raw);
  780. r = await apiCall('POST', '/api/health/nutrition/deficiency', { childId: 1003 }, t);
  781. record(F, 'parent', '营养缺乏分析', r.status === 200 && r.body && r.body.code === 200, r.body ? 'deficiency OK' : r.raw);
  782. }
  783. // ================================================================
  784. // FLOW 15: 活动管理 (activity-flow)
  785. // ================================================================
  786. async function testActivityFlow() {
  787. var F = '活动管理';
  788. log('\n========== ' + F + ' ==========');
  789. var t = tokens.parent;
  790. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  791. // 活动列表
  792. var r = await apiCall('POST', '/api/activity/list', {}, t);
  793. record(F, 'parent', '活动列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'activities OK' : r.raw);
  794. // 我的报名
  795. r = await apiCall('POST', '/api/activity/my-registrations', {}, t);
  796. record(F, 'parent', '我的报名', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my registrations OK' : r.raw);
  797. // --- 管理员 ---
  798. var t2 = tokens.admin;
  799. if (t2) {
  800. r = await apiCall('POST', '/api/admin/activity/list', {}, t2);
  801. record(F, 'admin', '管理端活动列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'admin activities OK' : r.raw);
  802. }
  803. }
  804. // ================================================================
  805. // FLOW 16: 内容与媒体 (content-media-flow)
  806. // ================================================================
  807. async function testContentFlow() {
  808. var F = '内容与媒体';
  809. log('\n========== ' + F + ' ==========');
  810. var t = tokens.parent;
  811. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  812. var r = await apiCall('POST', '/api/articles/list', {}, t);
  813. record(F, 'parent', '文章列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'articles OK' : r.raw);
  814. r = await apiCall('POST', '/api/articles/categories', {}, t);
  815. record(F, 'parent', '文章分类', r.status === 200 && r.body && r.body.code === 200, r.body ? 'categories OK' : r.raw);
  816. r = await apiCall('POST', '/api/articles/featured', {}, t);
  817. record(F, 'parent', '精选文章', r.status === 200 && r.body && r.body.code === 200, r.body ? 'featured OK' : r.raw);
  818. r = await apiCall('POST', '/api/articles/daily-tip', {}, t);
  819. record(F, 'parent', '每日贴士', r.status === 200 && r.body && r.body.code === 200, r.body ? 'daily tip OK' : r.raw);
  820. // 分享
  821. r = await apiCall('POST', '/api/share/my', {}, t);
  822. record(F, 'parent', '我的分享', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my shares OK' : r.raw);
  823. // 媒体
  824. r = await apiCall('POST', '/api/media/upload-text', { text: 'Test media text' }, t);
  825. record(F, 'parent', '保存文字记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'text upload OK' : r.body ? r.body.message : r.raw);
  826. }
  827. // ================================================================
  828. // FLOW 17: AI助手 (ai-assistant-flow)
  829. // ================================================================
  830. async function testAIFlow() {
  831. var F = 'AI助手';
  832. log('\n========== ' + F + ' ==========');
  833. var t = tokens.parent;
  834. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  835. // 会话列表
  836. var r = await apiCall('POST', '/api/ai/chat/conversations', {}, t);
  837. record(F, 'parent', 'AI会话列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'conversations OK' : r.raw);
  838. // AI管家会话
  839. r = await apiCall('POST', '/api/ai/butler/sessions/list', {}, t);
  840. record(F, 'parent', 'AI管家会话列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'butler sessions OK' : r.raw);
  841. // AI上下文(user_id 必须是字符串类型,后端用 (String) 强转)
  842. r = await apiCall('POST', '/api/ai/context', { user_id: "1002", intent_type: "general" }, t);
  843. record(F, 'parent', 'AI上下文', r.status === 200 && r.body && r.body.code === 200, r.body ? 'context OK' : r.raw);
  844. // 推荐搜索
  845. r = await apiCall('POST', '/api/recommend/search', { keyword: 'nutrition' }, t);
  846. record(F, 'parent', '推荐搜索', r.status === 200 && r.body && r.body.code === 200, r.body ? 'recommend search OK' : r.raw);
  847. }
  848. // ================================================================
  849. // FLOW 18: 会员与订阅 (membership-flow)
  850. // ================================================================
  851. async function testMembershipFlow() {
  852. var F = '会员与订阅';
  853. log('\n========== ' + F + ' ==========');
  854. var t = tokens.parent;
  855. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  856. var r = await apiCall('POST', '/api/membership/levels', {}, t);
  857. record(F, 'parent', '会员等级列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'levels OK' : r.raw);
  858. r = await apiCall('POST', '/api/membership/my', {}, t);
  859. record(F, 'parent', '我的会员信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my membership OK' : r.raw);
  860. r = await apiCall('POST', '/api/membership/level', {}, t);
  861. record(F, 'parent', '当前等级', r.status === 200 && r.body && r.body.code === 200, r.body ? 'current level OK' : r.raw);
  862. // 功能权限(需要 feature 参数)
  863. r = await apiCall('POST', '/api/membership/can-use', { feature: 'advance_analysis' }, t);
  864. record(F, 'parent', '功能权限', r.status === 200 && r.body && r.body.code === 200, r.body ? 'can-use OK' : r.raw);
  865. // 会员折扣(需要 amount 参数)
  866. r = await apiCall('POST', '/api/membership/discount', { amount: 1000 }, t);
  867. record(F, 'parent', '会员折扣', r.status === 200 && r.body && r.body.code === 200, r.body ? 'discount OK' : r.raw);
  868. // 订阅状态(需要familyId)
  869. r = await apiCall('POST', '/api/subscription/status?familyId=2001', {}, t);
  870. record(F, 'parent', '订阅状态', r.status === 200 && r.body && r.body.code === 200, r.body ? 'subscription status OK' : r.raw);
  871. r = await apiCall('POST', '/api/subscription/plans', {}, t);
  872. record(F, 'parent', '订阅计划', r.status === 200 && r.body && r.body.code === 200, r.body ? 'subscription plans OK' : r.raw);
  873. }
  874. // ================================================================
  875. // FLOW 19: 管理后台 (admin-overview-flow)
  876. // ================================================================
  877. async function testAdminFlow() {
  878. var F = '管理后台';
  879. log('\n========== ' + F + ' ==========');
  880. var t = tokens.admin;
  881. if (!t) { skip(F, 'admin', '所有测试', '未登录'); return; }
  882. // 用户管理
  883. var r = await apiCall('POST', '/api/admin/users', {}, t);
  884. record(F, 'admin', '用户列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'users OK' : r.raw);
  885. // 孩子管理
  886. r = await apiCall('POST', '/api/admin/children', {}, t);
  887. record(F, 'admin', '孩子列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'children OK' : r.raw);
  888. // 家庭管理
  889. r = await apiCall('POST', '/api/admin/families', {}, t);
  890. record(F, 'admin', '家庭列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'families OK' : r.raw);
  891. // 任务管理
  892. r = await apiCall('POST', '/api/admin/tasks', {}, t);
  893. record(F, 'admin', '任务列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'tasks OK' : r.raw);
  894. // 积分管理
  895. r = await apiCall('POST', '/api/admin/points/logs', {}, t);
  896. record(F, 'admin', '积分日志', r.status === 200 && r.body && r.body.code === 200, r.body ? 'points logs OK' : r.raw);
  897. // 规划师审核
  898. r = await apiCall('POST', '/api/admin/guide/applications/pending', {}, t);
  899. record(F, 'admin', '规划师待审批', r.status === 200 && r.body && r.body.code === 200, r.body ? 'guide pending OK' : r.raw);
  900. // 商品管理
  901. r = await apiCall('POST', '/api/admin/product/list', {}, t);
  902. record(F, 'admin', '商品列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'product list OK' : r.raw);
  903. // 订单管理
  904. r = await apiCall('POST', '/api/admin/product/order/list', {}, t);
  905. record(F, 'admin', '订单列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'order list OK' : r.raw);
  906. // 小游戏管理
  907. r = await apiCall('POST', '/api/mini-game/all', {}, t);
  908. record(F, 'admin', '所有游戏', r.status === 200 && r.body && r.body.code === 200, r.body ? 'all games OK' : r.raw);
  909. // 健康检查
  910. r = await apiCall('POST', '/api/system/health', {}, t);
  911. record(F, 'admin', '系统健康检查', r.status === 200 && r.body && r.body.code === 200, r.body ? 'health OK' : r.raw);
  912. }
  913. // ================================================================
  914. // FLOW 20: 规划师体系 (guide-system-flow)
  915. // ================================================================
  916. async function testGuideFlow() {
  917. var F = '规划师体系';
  918. log('\n========== ' + F + ' ==========');
  919. var t = tokens.teacher;
  920. if (!t) { skip(F, 'teacher', '所有测试', '未登录'); return; }
  921. // 个人信息
  922. var r = await apiCall('POST', '/api/guide/my', {}, t);
  923. record(F, 'teacher', '我的信息', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my info OK' : r.raw);
  924. r = await apiCall('POST', '/api/guide/my/packages', {}, t);
  925. record(F, 'teacher', '我的套餐', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my packages OK' : r.raw);
  926. // 团队管理
  927. r = await apiCall('POST', '/api/guide/team/my-team', {}, t);
  928. record(F, 'teacher', '我的团队', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my team OK' : r.raw);
  929. r = await apiCall('POST', '/api/guide/team/members', {}, t);
  930. record(F, 'teacher', '团队成员', r.status === 200 && r.body && r.body.code === 200, r.body ? 'team members OK' : r.raw);
  931. // 绑定家庭
  932. r = await apiCall('POST', '/api/guide/families/bound-families', {}, t);
  933. record(F, 'teacher', '绑定家庭', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'bound families OK' : r.raw);
  934. // 任务模板
  935. r = await apiCall('POST', '/api/guide/packages/list', {}, t);
  936. record(F, 'teacher', '模板列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'package list OK' : r.raw);
  937. // 训练方案
  938. r = await apiCall('POST', '/api/guide/training-plans/list', {}, t);
  939. record(F, 'teacher', '方案列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'training plans OK' : r.raw);
  940. // 活动管理
  941. r = await apiCall('POST', '/api/guide/activities/list', {}, t);
  942. record(F, 'teacher', '活动列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'activities OK' : r.raw);
  943. // 成长记录
  944. r = await apiCall('POST', '/api/guide/record/my-records', {}, t);
  945. record(F, 'teacher', '我的记录', r.status === 200 && r.body && r.body.code === 200, r.body ? 'records OK' : r.raw);
  946. // 咨询消息
  947. r = await apiCall('POST', '/api/guide/messages/list', {}, t);
  948. record(F, 'teacher', '咨询列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'messages OK' : r.raw);
  949. // 订单佣金
  950. r = await apiCall('POST', '/api/guide/orders/list', {}, t);
  951. record(F, 'teacher', '订单列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'orders OK' : r.raw);
  952. // 仪表盘
  953. r = await apiCall('POST', '/api/guide/families/dashboard-stats', {}, t);
  954. record(F, 'teacher', '仪表盘统计', r.status === 200 && r.body && r.body.code === 200, r.body ? 'dashboard stats OK' : r.raw);
  955. }
  956. // ================================================================
  957. // FLOW 21: 圈子与联系人 (circle-contact-flow)
  958. // ================================================================
  959. async function testCircleFlow() {
  960. var F = '圈子与联系人';
  961. log('\n========== ' + F + ' ==========');
  962. var t = tokens.parent;
  963. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  964. var r = await apiCall('POST', '/api/circle/my-circles', { memberId: 1003 }, t);
  965. record(F, 'parent', '我的圈子', r.status === 200 && r.body && r.body.code === 200, r.body ? 'my circles OK' : r.raw);
  966. r = await apiCall('POST', '/api/circle/discover', { childId: 1003 }, t);
  967. record(F, 'parent', '发现圈子', r.status === 200 && r.body && r.body.code === 200, r.body ? 'discover OK' : r.raw);
  968. // 健康圈子
  969. r = await apiCall('POST', '/api/health/circle/list', {}, t);
  970. record(F, 'parent', '健康圈子列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'health circle OK' : r.raw);
  971. // 联系人
  972. r = await apiCall('POST', '/api/contact/list', {}, t);
  973. record(F, 'parent', '联系人列表', r.status === 200 && r.body && r.body.code === 200, r.body && r.body.data ? 'contacts OK' : r.raw);
  974. // 家庭挑战
  975. r = await apiCall('POST', '/api/health/challenge/list', {}, t);
  976. record(F, 'parent', '家庭挑战列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'challenges OK' : r.raw);
  977. }
  978. // ================================================================
  979. // FLOW 22: 知识库与邀请 (knowledge-invite-flow)
  980. // ================================================================
  981. async function testKnowledgeFlow() {
  982. var F = '知识库与邀请';
  983. log('\n========== ' + F + ' ==========');
  984. // 管理员:知识库
  985. var t = tokens.admin;
  986. if (t) {
  987. var r = await apiCall('POST', '/api/health/knowledge/list', {}, t);
  988. record(F, 'admin', '知识库列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'kb list OK' : r.raw);
  989. r = await apiCall('POST', '/api/health/knowledge/v3/bacteria/query', { name: 'lactobacillus' }, t);
  990. record(F, 'admin', '菌属查询', r.status === 200 && r.body && r.body.code === 200, r.body ? 'bacteria query OK' : r.raw);
  991. r = await apiCall('POST', '/api/health/knowledge/v3/food/query', { name: 'apple' }, t);
  992. record(F, 'admin', '食物营养查询', r.status === 200 && r.body && r.body.code === 200, r.body ? 'food query OK' : r.raw);
  993. }
  994. // 用户:邀请码
  995. var t2 = tokens.parent;
  996. if (t2) {
  997. var r = await apiCall('POST', '/api/invite/code', {}, t2);
  998. record(F, 'parent', '生成邀请码', r.status === 200 && r.body && r.body.code === 200, r.body ? 'invite code OK' : r.raw);
  999. r = await apiCall('POST', '/api/invite/list', {}, t2);
  1000. record(F, 'parent', '邀请列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'invite list OK' : r.raw);
  1001. r = await apiCall('POST', '/api/invite/summary', {}, t2);
  1002. record(F, 'parent', '邀请汇总', r.status === 200 && r.body && r.body.code === 200, r.body ? 'invite summary OK' : r.raw);
  1003. // 里程碑
  1004. r = await apiCall('POST', '/api/invite/milestone/list', {}, t2);
  1005. record(F, 'parent', '里程碑列表', r.status === 200 && r.body && r.body.code === 200, r.body ? 'milestones OK' : r.raw);
  1006. }
  1007. }
  1008. // ================================================================
  1009. // FLOW 23: 其他功能 (extra-features-flow)
  1010. // ================================================================
  1011. async function testExtraFlow() {
  1012. var F = '其他功能';
  1013. log('\n========== ' + F + ' ==========');
  1014. var t = tokens.parent;
  1015. if (!t) { skip(F, 'parent', '所有测试', '未登录'); return; }
  1016. // 吉祥物
  1017. var r = await apiCall('POST', '/api/user/mascot/get', {}, t);
  1018. record(F, 'parent', '获取吉祥物', r.status === 200 && r.body && r.body.code === 200, r.body ? 'mascot OK' : r.raw);
  1019. // 每日反馈
  1020. r = await apiCall('POST', '/api/feedback/today', {}, t);
  1021. record(F, 'parent', '今日反馈', r.status === 200 && r.body && r.body.code === 200, r.body ? 'today feedback OK' : r.raw);
  1022. r = await apiCall('POST', '/api/feedback/recent', {}, t);
  1023. record(F, 'parent', '最近反馈', r.status === 200 && r.body && r.body.code === 200, r.body ? 'recent feedback OK' : r.raw);
  1024. // 生成小程序码(仅支持 type=register 且需要 referralCode)
  1025. r = await apiCall('POST', '/api/common/qrcode', { type: 'register', referralCode: 'TEST2001' }, t);
  1026. record(F, 'parent', '生成小程序码', r.status === 200 && r.body && r.body.code === 200, r.body ? 'qrcode OK' : r.raw);
  1027. // 引导进度
  1028. r = await apiCall('POST', '/api/onboarding/progress', {}, t);
  1029. record(F, 'parent', '引导进度', r.status === 200 && r.body && r.body.code === 200, r.body ? 'onboarding OK' : r.raw);
  1030. }
  1031. // ================================================================
  1032. // MAIN RUNNER
  1033. // ================================================================
  1034. async function main() {
  1035. console.log('============================================================');
  1036. console.log(' 流程级一级测试用例 — FLOW L1 TEST SUITE');
  1037. console.log(' Target: ' + BASE_URL);
  1038. console.log(' Date: ' + new Date().toISOString());
  1039. console.log('============================================================\n');
  1040. // Step 1: Login all roles
  1041. console.log('--- LOGIN ALL ROLES ---');
  1042. await loginAll();
  1043. console.log('');
  1044. // Step 2: Run all flow tests
  1045. await testAuthFlow();
  1046. await testFamilyFlow();
  1047. await testTaskFlow();
  1048. await testWishFlow();
  1049. await testEnergyFlow();
  1050. await testCheckinFlow();
  1051. await testGrowthFlow();
  1052. await testDanFlow();
  1053. await testShopFlow();
  1054. await testPointsFlow();
  1055. await testMinigameFlow();
  1056. await testNutritionFlow();
  1057. await testMindFlow();
  1058. await testHealthDetectionFlow();
  1059. await testActivityFlow();
  1060. await testContentFlow();
  1061. await testAIFlow();
  1062. await testMembershipFlow();
  1063. await testAdminFlow();
  1064. await testGuideFlow();
  1065. await testCircleFlow();
  1066. await testKnowledgeFlow();
  1067. await testExtraFlow();
  1068. // Step 3: Summary
  1069. console.log('\n============================================================');
  1070. console.log(' TEST RESULTS SUMMARY');
  1071. console.log('============================================================');
  1072. console.log(' ✅ Passed: ' + results.passed);
  1073. console.log(' ❌ Failed: ' + results.failed);
  1074. console.log(' ⏭️ Skipped: ' + results.skipped);
  1075. console.log(' Total: ' + (results.passed + results.failed + results.skipped));
  1076. console.log('============================================================');
  1077. // Group failures by flow
  1078. var failures = results.details.filter(function(d) { return d.passed === false; });
  1079. if (failures.length > 0) {
  1080. console.log('\n--- FAILURES ---');
  1081. var byFlow = {};
  1082. failures.forEach(function(f) {
  1083. if (!byFlow[f.flow]) byFlow[f.flow] = [];
  1084. byFlow[f.flow].push(f);
  1085. });
  1086. for (var flow in byFlow) {
  1087. console.log('\n[' + flow + ']');
  1088. byFlow[flow].forEach(function(f) {
  1089. console.log(' ❌ ' + f.role + ' | ' + f.step + ' — ' + (f.detail || ''));
  1090. });
  1091. }
  1092. }
  1093. // Group issues
  1094. if (results.issues.length > 0) {
  1095. console.log('\n--- ISSUES ---');
  1096. results.issues.forEach(function(iss) {
  1097. console.log('⚠️ [' + iss.flow + '] ' + iss.role + ' | ' + iss.step + ': ' + iss.description);
  1098. });
  1099. }
  1100. // Flows with all passes
  1101. var allFlows = {};
  1102. results.details.forEach(function(d) {
  1103. if (!allFlows[d.flow]) allFlows[d.flow] = { pass: 0, fail: 0, skip: 0 };
  1104. if (d.passed === true) allFlows[d.flow].pass++;
  1105. else if (d.passed === false) allFlows[d.flow].fail++;
  1106. else allFlows[d.flow].skip++;
  1107. });
  1108. console.log('\n--- FLOW STATUS ---');
  1109. for (var f in allFlows) {
  1110. var s = allFlows[f];
  1111. var status = s.fail === 0 ? '✅' : '❌';
  1112. console.log(status + ' ' + f + ': ' + s.pass + '/' + (s.pass + s.fail) + ' passed, ' + s.skip + ' skipped');
  1113. }
  1114. console.log('\n============================================================');
  1115. console.log(' DONE');
  1116. console.log('============================================================');
  1117. }
  1118. main().catch(function(err) {
  1119. console.error('FATAL:', err);
  1120. process.exit(1);
  1121. });